from xml.etree.ElementTree import Element, SubElement
from typing import Union

class _PVName(object):
    def pv_name(self, name: str) -> None:
        """
        Add PV name to widget

        :param name: PV name
        """
        self._shared.generic_property(self.root, 'pv_name', name)

class _Font(object):
    def predefined_font(self, name: object) -> None:
        """
        Add named font property to widget

        :param name: <phoebusgen.fonts> Font name
        """
        self._shared.create_named_font_element(self.root, 'font', name)

    def font_family(self, family: str) -> None:
        """
        Change font family property for widget

        :param family: Font Family name
        """
        child_elem = self._shared.get_font_element(self.root, 'font')
        child_elem.attrib['family'] = str(family)

    def font_size(self, size: int) -> None:
        """
        Change font size property for widget

        :param size: Font size
        """
        if isinstance(size, int) or isinstance(size, float):
            child_elem = self._shared.get_font_element(self.root, 'font')
            child_elem.attrib['size'] = str(int(size))
        else:
            print('Font size must be a number! Not: {}'.format(size))

    def font_style_bold(self) -> None:
        """
        Change font style to Bold
        """
        self._shared.add_font_style(self.root, 'font', self._shared.FontStyle.bold)

    def font_style_italic(self) -> None:
        """
        Change font style to Italic
        """
        self._shared.add_font_style(self.root, 'font', self._shared.FontStyle.italic)

    def font_style_bold_italic(self) -> None:
        """
        Change font style to Bold & Italic
        """
        self._shared.add_font_style(self.root, 'font', self._shared.FontStyle.bold_and_italic)

    def font_style_regular(self) -> None:
        """
        Change font style to Regular
        """
        self._shared.add_font_style(self.root, 'font', self._shared.FontStyle.regular)

class _TitleFont(object):
    def predefined_title_font(self, name: object) -> None:
        """
        Add named title font property to widget

        :param name: <phoebusgen.fonts> Font name
        """
        self._shared.create_named_font_element(self.root, 'title_font', name)

    def title_font_family(self, family: str) -> None:
        """
        Change title font family property for widget

        :param family: Font Family name
        """
        child_elem = self._shared.get_font_element(self.root, 'title_font')
        child_elem.attrib['family'] = str(family)

    def title_font_size(self, size: int) -> None:
        """
        Change title font size property for widget

        :param size: Font size
        """
        if isinstance(size, int) or isinstance(size, float):
            child_elem = self._shared.get_font_element(self.root, 'title_font')
            child_elem.attrib['size'] = str(int(size))
        else:
            print('Font size must be a number! Not: {}'.format(size))

    def title_font_style_bold(self) -> None:
        """
        Change title font style to Bold
        """
        self._shared.add_font_style(self.root, 'title_font', self._shared.FontStyle.bold)

    def title_font_style_italic(self) -> None:
        """
        Change title font style to Italic
        """
        self._shared.add_font_style(self.root, 'title_font', self._shared.FontStyle.italic)

    def title_font_style_bold_italic(self) -> None:
        """
        Change title font style to Bold & Italic
        """
        self._shared.add_font_style(self.root, 'title_font', self._shared.FontStyle.bold_and_italic)

    def title_font_style_regular(self) -> None:
        """
        Change title font style to Regular
        """
        self._shared.add_font_style(self.root, 'title_font', self._shared.FontStyle.regular)

class _ScaleFont(object):
    def predefined_scale_font(self, name: object) -> None:
        """
        Add named scale font property to widget

        :param name: <phoebusgen.fonts> Font name
        """
        self._shared.create_named_font_element(self.root, 'scale_font', name)

    def scale_font_family(self, family: str) -> None:
        """
        Change scale font family property for widget

        :param family: Font Family name
        """
        child_elem = self._shared.get_font_element(self.root, 'scale_font')
        child_elem.attrib['family'] = str(family)

    def scale_font_size(self, size: int) -> None:
        """
        Change scale font size property for widget

        :param size: Font size
        """
        if isinstance(size, int) or isinstance(size, float):
            child_elem = self._shared.get_font_element(self.root, 'scale_font')
            child_elem.attrib['size'] = str(int(size))
        else:
            print('Font size must be a number! Not: {}'.format(size))

    def scale_font_style_bold(self) -> None:
        """
        Change scale font style to Bold
        """
        self._shared.add_font_style(self.root, 'scale_font', self._shared.FontStyle.bold)

    def scale_font_style_italic(self) -> None:
        """
        Change scale font style to Italic
        """
        self._shared.add_font_style(self.root, 'scale_font', self._shared.FontStyle.italic)

    def scale_font_style_bold_italic(self) -> None:
        """
        Change scale font style to Bold & Italic
        """
        self._shared.add_font_style(self.root, 'scale_font', self._shared.FontStyle.bold_and_italic)

    def scale_font_style_regular(self) -> None:
        """
        Change scale font style to Regular
        """
        self._shared.add_font_style(self.root, 'scale_font', self._shared.FontStyle.regular)


class _LabelFont(object):
    def predefined_label_font(self, name: object) -> None:
        """
        Add named label font property to widget

        :param name: <phoebusgen.fonts> Font name
        """
        self._shared.create_named_font_element(self.root, 'label_font', name)

    def label_font_family(self, family: str) -> None:
        """
        Change label font family property for widget

        :param family: Font Family name
        """
        child_elem = self._shared.get_font_element(self.root, 'label_font')
        child_elem.attrib['family'] = str(family)

    def label_font_size(self, size: int) -> None:
        """
        Change label font size property for widget

        :param size: Font size
        """
        if isinstance(size, int) or isinstance(size, float):
            child_elem = self._shared.get_font_element(self.root, 'label_font')
            child_elem.attrib['size'] = str(int(size))
        else:
            print('Font size must be a number! Not: {}'.format(size))

    def label_font_style_bold(self) -> None:
        """
        Change label font style to Bold
        """
        self._shared.add_font_style(self.root, 'label_font', self._shared.FontStyle.bold)

    def label_font_style_italic(self) -> None:
        """
        Change label font style to Italic
        """
        self._shared.add_font_style(self.root, 'label_font', self._shared.FontStyle.italic)

    def label_font_style_bold_italic(self) -> None:
        """
        Change label font style to Bold & Italic
        """
        self._shared.add_font_style(self.root, 'label_font', self._shared.FontStyle.bold_and_italic)

    def label_font_style_regular(self) -> None:
        """
        Change label font style to Regular
        """
        self._shared.add_font_style(self.root, 'label_font', self._shared.FontStyle.regular)

class _ForegroundColor(object):
    def predefined_foreground_color(self, name: object) -> None:
        """
        Add named foreground color to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'foreground_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def foreground_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add foreground color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'foreground_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _BackgroundColor(object):
    def predefined_background_color(self, name: object) -> None:
        """
        Add named background color to widget
        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'background_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def background_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add background color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'background_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _Transparent(object):
    def transparent(self, transparent: bool = False) -> None:
        """
        Change widget transparent property. Default arg is False

        :param transparent: Is widget transparent?
        """
        self._shared.boolean_property(self.root, 'transparent', transparent)

class _Format(object):
    # this should use an enum
    def format(self, format_val: str) -> None:
        """
        Add format property to widget, i.e. Decimal, Exponential, Engineering, String, etc

        :param format_val: Format String name
        :return:
        """
        val = str(format_val).lower()
        try:
            v = self._shared.formats_array.index(val)
        except ValueError:
            print('Invalid format. Given format {}'.format(format_val))
            return
        self._shared.generic_property(self.root, 'format', v)

class _Precision(object):
    def precision(self, val: int) -> None:
        """
        Add precision value to widget

        :param val: Precision value
        """
        self._shared.integer_property(self.root, 'precision', val)

class _ShowUnits(object):
    def show_units(self, show: bool = True) -> None:
        """
        Change show units property to widget. Default arg is True

        :param show: Show units?
        """
        self._shared.boolean_property(self.root, 'show_units', show)

class _HorizontalAlignment(object):
    def _add_horizontal_alignment(self, alignment):
        if not isinstance(alignment, self._shared.HorizontalAlignment):
            print('The component parameter must be of type HorizontalAlignment enum! Not: {}'.format(type(alignment)))
            return
        self._shared.generic_property(self.root, 'horizontal_alignment', alignment.value)

    def horizontal_alignment_left(self) -> None:
        """
        Change horizontal alignmnet to Left
        """
        self._add_horizontal_alignment(self._shared.HorizontalAlignment.left)

    def horizontal_alignment_center(self) -> None:
        """
        Change horizontal alignment to center
        """
        self._add_horizontal_alignment(self._shared.HorizontalAlignment.center)

    def horizontal_alignment_right(self) -> None:
        """
        Change horizontal alignment to right
        """
        self._add_horizontal_alignment(self._shared.HorizontalAlignment.right)

class _VerticalAlignment(object):
    def _add_vertical_alignment(self, alignment):
        if not isinstance(alignment, self._shared.VerticalAlignment):
            print('The component parameter must be of type VerticalAlignment enum! Not: {}'.format(type(alignment)))
            return
        self._shared.generic_property(self.root, 'vertical_alignment', alignment.value)

    def vertical_alignment_top(self) -> None:
        """
        Change vertical alignment to Top
        """
        self._add_vertical_alignment(self._shared.VerticalAlignment.top)

    def vertical_alignment_middle(self) -> None:
        """
        Change vertical alignment to Middle
        """
        self._add_vertical_alignment(self._shared.VerticalAlignment.middle)

    def vertical_alignment_bottom(self) -> None:
        """
        Change vertical alignment to Bottom
        """
        self._add_vertical_alignment(self._shared.VerticalAlignment.bottom)

class _WrapWords(object):
    def wrap_words(self, wrap: bool = True) -> None:
        """
        Change wrap words property. Default arg value is True

        :param wrap: Wrap words?
        """
        self._shared.boolean_property(self.root, 'wrap_words', wrap)

class _Text(object):
    def text(self, text: str) -> None:
        """
        Add text property to widget

        :param text: Text string
        """
        self._shared.generic_property(self.root, 'text', text)

class _AutoSize(object):
    def auto_size(self, auto: bool = True) -> None:
        """
        Change auto size property on widget. Default arg is True

        :param auto: Auto size widget?
        """
        self._shared.boolean_property(self.root, 'auto_size', auto)

# 0, 90, 180, -90
class _RotationStep(object):
    def _add_rotation_step(self, rotation):
        if not isinstance(rotation, self._shared.RotationStep):
            print('The component parameter must be of type Rotation enum! Not: {}'.format(type(rotation)))
            return
        self._shared.generic_property(self.root, 'rotation_step', rotation.value)

    def rotation_step_0(self) -> None:
        """
        Change rotation step value to 0
        """
        self._add_rotation_step(self._shared.RotationStep.zero)

    def rotation_step_90(self) -> None:
        """
        Change rotation step value to 90
        """
        self._add_rotation_step(self._shared.RotationStep.ninety)

    def rotation_step_180(self) -> None:
        """
        Change rotation step value to 180
        """
        self._add_rotation_step(self._shared.RotationStep.one_hundred_eighty)

    def rotation_step_negative_90(self) -> None:
        """
        Change rotation step value to -90
        """
        self._add_rotation_step(self._shared.RotationStep.negative_ninety)

class _Border(object):
    def border_width(self, width: int) -> None:
        """
        Add border width property to widget

        :param width: Border width value
        """
        self._shared.integer_property(self.root, 'border_width', width)

    def predefined_border_color(self, name: object) -> None:
        """
        Change border color with named color

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'border_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def border_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Change border color with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'border_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _Macro(object):
    def macro(self, name: str, val: Union[str, int, float]) -> None:
        """
        Add macro to widget

        :param name: Macro name
        :param val: Macro value
        """
        self._shared.add_macro(name, val, None)

class _Bit(object):
    def bit(self, val: int = -1) -> None:
        """
        Add bit property to widget. Default arg is -1

        :param val: Bit number
        """
        self._shared.integer_property(self.root, 'bit', val)

class _OffColor(object):
    def predefined_off_color(self, name: object) -> None:
        """
        Add named color for Off Color property

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'off_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def off_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Off color property using RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'off_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _Off(_OffColor):
    def off_label(self, label: str) -> None:
        """
        Add Off label string to widget

        :param label: label
        """
        self._shared.generic_property(self.root, 'off_label', label)

class _OffImage(_Off):
    def off_image(self, image_file: str) -> None:
        """
        Add image for off property using file name

        :param image_file: Path to image file
        """
        self._shared.generic_property(self.root, 'off_image', image_file)

class _OnColor(object):
    def predefined_on_color(self, name: object) -> None:
        """
        Add named color for On Color property

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'on_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def on_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add On color property using RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'on_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _On(_OnColor):
    def on_label(self, label: str) -> None:
        """
        Add On label string to widget

        :param label: label
        """
        self._shared.generic_property(self.root, 'on_label', label)

class _OnImage(_On):
    def on_image(self, image_file: str) -> None:
        """
        Add image for on property using file name

        :param image_file: Path to image file
        """
        self._shared.generic_property(self.root, 'on_image', image_file)

class _LineColor(object):
    def predefined_line_color(self, name: object) -> None:
        """
        Add named line color property

        :param name: <phoebusgen.colors> Predefined color name
        """
        if (self.root.attrib['type'] == 'group'):
            self.version('3.0.0')
        e = self._shared.create_element(self.root, 'line_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def line_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add line color property using RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        if (self.root.attrib['type'] == 'group'):
            self.version('3.0.0')
        e = self._shared.create_element(self.root, 'line_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _LineWidth(object):
    def line_width(self, width: int) -> None:
        """
        Add line width property to widget

        :param width: Line width
        """
        self._shared.integer_property(self.root, 'line_width', width)

class _Corner(object):
    def corner_width(self, width: int) -> None:
        """
        Add corner width property to widget

        :param width: Corner width
        """
        self._shared.integer_property(self.root, 'corner_width', width)

    def corner_height(self, height: int) -> None:
        """
        Add corner height property to widget

        :param height: Corner height
        """
        self._shared.integer_property(self.root, 'corner_height', height)

class _Square(object):
    def square(self, val: bool) -> None:
        """
        Change widget square property

        :param val: Is width square?
        """
        self._shared.boolean_property(self.root, 'square', val)

class _LabelsFromPV(object):
    def labels_from_pv(self, val: bool) -> None:
        """
        Change labels from pv property for widget

        :param val: Show labels from PV?
        """
        self._shared.boolean_property(self.root, 'labels_from_pv', val)

class _AlarmBorder(object):
    def alarm_border(self, val: bool) -> None:
        """
        Add alarm border property ON/OFF for widget

        :param val: Add alarm border to widget?
        """
        self._shared.boolean_property(self.root, 'border_alarm_sensitive', val)

class _Enabled(object):
    def enabled(self, val: bool) -> None:
        """
        Add enabled property to widget

        :param val: Is widget enabled?
        """
        self._shared.boolean_property(self.root, 'enabled', val)

class _Confirmation(object):
    def confirmation_dialog(self, message: str, password: str = None) -> None:
        """
        Add confirmation dialog to widget, i.e. Are you sure? . Default arg for password is None (no pw)

        :param message: Confirmation dialog message
        :param password: Password for dialog. Stored in plain text XML
        """
        self._shared.boolean_property(self.root, 'show_confirm_dialog', True)
        self._shared.generic_property(self.root, 'confirm_message', message)
        if password is not None:
            self._shared.generic_property(self.root, 'password', password)

    def disable_confirmation_dialog(self) -> None:
        """
        Turn off confirmation dialog for widget
        """
        self._shared.boolean_property(self.root, 'show_confirm_dialog', False)

class _MultiLine(object):
    def multi_line(self, val: bool) -> None:
        """
        Change Multi Line property for widget

        :param val: Use multi line?
        """
        self._shared.boolean_property(self.root, 'multi_line', val)

class _Angle(object):
    def angle_start(self, val: Union[float, int]) -> None:
        """
        Add angle start value for widget

        :param val: Start angle value
        """
        self._shared.number_property(self.root, 'start_angle', val)

    def angle_size(self, val: Union[float, int]) -> None:
        """
        Add angle size value for widget

        :param val: Angle size value
        """
        self._shared.number_property(self.root, 'total_angle', val)

class _Rotation(object):
    def rotation(self, val: Union[float, int]) -> None:
        """
        Add rotation value for widget

        :param val: Rotation value
        """
        self._shared.number_property(self.root, 'rotation', val)

class _File(object):
    def file(self, val: str) -> None:
        """
        Add file name property to widget

        :param val: File name
        """
        self._shared.generic_property(self.root, 'file', val)

class _StretchToFit(object):
    def stretch_to_fit(self, val: bool) -> None:
        """
        Add stretch to fit property to widget

        :param val: Stretch widget to fit?
        """
        self._shared.boolean_property(self.root, 'stretch_image', val)

class _Actions(object):
    def _add_action(self, action_type, description, args, isScript, macros=None):
        root_action = self.root.find('actions')
        if root_action is None:
            root_action = SubElement(self.root, 'actions')
        action = SubElement(root_action, 'action')
        action.attrib['type'] = action_type
        sub = SubElement(action, 'description')
        sub.text = str(description)
        if macros:
            if not isinstance(macros, dict):
                print('macros parameter must be of type dict, not: {}'.format(type(macros)))
            else:
                for key, val in macros.items():
                    self._shared.add_macro(key, val, action)
        if isScript:
            script_element = SubElement(action, 'script')
            script_element.attrib['file'] = args['file']
            if args.get('script') is not None:
                text_element = SubElement(script_element, 'text')
                text_element.text = args['script']
        else:
            for arg, val in args.items():
                sub = SubElement(action, arg)
                sub.text = str(val)

    def action_execute_as_one(self, val: bool) -> None:
        """
        Add execute all actions as one property to widget (execute all actions on button press)

        :param val: Execute all actions as one?
        """
        if isinstance(val, bool):
            action = str(val).lower()
        elif isinstance(val, int):
            action = str(bool(val)).lower()
        elif val.lower() == 'true' or val.lower() == 'false':
            action = val.lower()
        else:
            print('action_execute_as_one must take a boolean value! Not: {}'.format(val))
            return
        root_action = self.root.find('actions')
        if root_action is None:
            root_action = SubElement(self.root, 'actions')
        root_action.attrib['execute_as_one'] = action

    # should actions be their own class?
    # target should be an enum
    def action_open_display(self, file: str, target: str, description: str = None, macros: dict = None) -> None:
        """
        Add open display action to widget. description and macros are optional params

        :param file: File name to open
        :param target: <specific strings only> tab, replace, window
        :param description: Description of action. Default is None
        :param macros: Dictionary of macros. key=macro name and value=macro value
        :return:
        """
        if description is None:
            description = 'Open Display'
        possible_targets = ['tab', 'replace', 'window']
        if target.lower() not in possible_targets:
            print('Target must be one of {}, not: {}'.format(possible_targets, target))
            return
        if macros is not None and not isinstance(macros, dict):
            print('The macro parameter must be a dictionary with key=MacroName and val=MacroValue')
            return
        args = {'file': file, 'target': target.lower()}
        self._add_action('open_display', description, args, False, macros)

    def action_write_pv(self, pv_name: str, value: Union[str, int, float], description: str = None) -> None:
        """
        Add Write PV action to widget. description is optional

        :param pv_name: PV name to write to
        :param value: Value to write to PV
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Write PV'
        args = {'pv_name': pv_name, 'value': value}
        self._add_action('write_pv', description, args, False)

    def action_execute_command(self, command: str, description: str = None) -> None:
        """
        Add Execute Command action to widget. description is optional

        :param command: Command to run in action
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Execute Command'
        args = {'command': command}
        self._add_action('command', description, args, False)

    def action_execute_python_script(self, script: str, description: str = None) -> None:
        """
        Add Execute Command action to widget. description is optional

        :param script: Python script to execute as action
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Execute Script'
        args = {'file': 'EmbeddedPy', 'script': script}
        self._add_action('execute', description, args, True)

    def action_execute_javascript_script(self, script: str, description: str = None) -> None:
        """
        Add Execute Command action to widget. description is optional

        :param script: Javascript code to execute as action
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Execute Script'
        args = {'file': 'EmbeddedJs', 'script': script}
        self._add_action('execute', description, args, True)

    def action_execute_external_script(self, file_name: str, description: str = None) -> None:
        """
        Add Execute Command action to widget. description is optional

        :param file_name: External script file name
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Execute Script'
        args = {'file': file_name}
        self._add_action('execute', description, args, True)

    def action_open_file(self, file: str, description: str = None) -> None:
        """
        Add Open File action to widget. description is optional

        :param file: File name to open
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Open File'
        args = {'file': file}
        self._add_action('open_file', description, args, False)

    def action_open_webpage(self, url: str, description: str = None) -> None:
        """
        Add Open Webpage action to widget. description is optional

        :param url: URL to open
        :param description: Description of action. Default is None
        """
        if description is None:
            description = 'Open Webpage'
        args = {'url': url}
        self._add_action('open_webpage', description, args, False)

class _Label(object):
    def label(self, val: str) -> None:
        """
        Add label to widget

        :param val: Label
        """
        self._shared.generic_property(self.root, 'label', val)

class _Horizontal(object):
    def horizontal(self, val: bool) -> None:
        """
        Change horizontal property of widget

        :param val: Is widget horizontal?
        """
        self._shared.boolean_property(self.root, 'horizontal', val)

class _Items(object):
    def item(self, item_text: str) -> None:
        """
        Add item property to widget

        :param item_text: Item text string
        """
        root_item = self.root.find('items')
        if root_item is None:
            root_item = SubElement(self.root, 'items')
        self._shared.list_property(root_item, 'item', item_text)

class _Traces(object):
    def add_trace(self, trace: object) -> None:
        """
        Add trace property to widget

        :param trace: trace object
        """
        if (type(self).__name__ not in str(type(trace))):
            print('Chart type and trace type do not match.')
            return
        root_traces = self.root.find('traces')
        if root_traces is None:
            root_traces = SubElement(self.root, 'traces')
            root_traces.append(trace.root)
        else:
            root_trace = root_traces.findall('trace')
            if trace.root in root_trace:
                print('Trace already exists.')
            else:
                root_traces.append(trace.root)

    def remove_trace(self, trace: object) -> None:
        """
        Removes trace from widget

        :param trace: trace object
        """
        root_traces = self.root.find('traces')
        if root_traces is None:
            print('This graph has no traces.')
        else:
            root_trace = root_traces.findall('trace')
            if trace.root in root_trace:
                root_traces.remove(trace.root)
            else:
                print('Trace does not exist.')
            if not root_trace:
                self.root.remove(root_traces)   # removes hanging <trace/> tag from empty element

class _TraceType(object):
    def trace_type_none(self) -> None:
        """
        Change trace_type to none
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.none.value)

    def trace_type_line(self) -> None:
        """
        Change trace_type to line
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.line.value)

    def trace_type_step(self) -> None:
        """
        Change trace_type to step
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.step.value)

    def trace_type_error_bars(self) -> None:
        """
        Change trace_type to error bars
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.err_bars.value)

    def trace_type_line_error_bars(self) -> None:
        """
        Change trace_type to line & error bars
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.line_err_bars.value)

    def trace_type_bars(self) -> None:
        """
        Change trace_type to bars
        """
        self._shared.integer_property(self.root, 'trace_type', self._shared.TraceType.bars.value)

class _PointType(object):
    def point_type_none(self) -> None:
        """
        Change point type to none
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.none.value)

    def point_type_squares(self) -> None:
        """
        Change point type to squares
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.squares.value)

    def point_type_circles(self) -> None:
        """
        Change point type to circles
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.circles.value)

    def point_type_diamonds(self) -> None:
        """
        Change point type to diamonds
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.diamonds.value)

    def point_type_x(self) -> None:
        """
        Change point type to x
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.x.value)

    def point_type_triangles(self) -> None:
        """
        Change point type to triangles
        """
        self._shared.integer_property(self.root, 'point_type', self._shared.PointType.triangles.value)

class _PointSize(object):
    def point_size(self, size: int) -> None:
        """
        Set trace point size

        :param size: Trace point size
        """
        self._shared.integer_property(self.root, 'point_size', size)

class _Color(object):
    def color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Fill Color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_color(self, name: object) -> None:
        """
        Add named color to widget
        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _Name(object):
    def name(self, name: str) -> None:
        """
        Change trace name

        :param name: Trace name
        """
        self._shared.generic_property(self.root, 'name', name)

class _Axis(object):
    def axis(self, axis: int) -> None:
        """
        Determine which y-axis the trace is for

        :param axis: Trace y-axis
        """
        self._shared.integer_property(self.root, 'axis', axis)

class _XPV(object):
    def x_pv(self, pv: str) -> None:
        """
        Change trace x_pv value

        :param pv: Trace x_pv
        """
        self._shared.generic_property(self.root, 'x_pv', pv)

class _YPV(object):
    def y_pv(self, pv: str) -> None:
        """
        Change trace y_pv value

        :param pv: Trace y_pv
        """
        self._shared.generic_property(self.root, 'y_pv', pv)

class _ErrPV(object):
    def err_pv(self, pv: str) -> None:
        """
        Change trace err_pv value

        :param pv: Trace err_pv
        """
        self._shared.generic_property(self.root, 'err_pv', pv)

class _WidthPV(object):
    def width_pv(self, pv: str) -> None:
        """
        Change ROI width_pv value

        :param pv: ROI width_pv
        """
        self._shared.generic_property(self.root, 'width_pv', pv)

class _HeightPV(object):
    def height_pv(self, pv: str) -> None:
        """
        Change ROI height_pv value

        :param pv: ROI height_pv
        """
        self._shared.generic_property(self.root, 'height_pv', pv)

class _OnRight(object):
    def on_right(self, right: bool) -> None:
        """
        Change y-axis position on screen (left or right)
        :param right: Is y-axis on right side of screen?
        """
        self._shared.boolean_property(self.root, 'on_right', right)

class _YAxes(object):
    def add_y_axis(self, axis: object) -> None:
        """
        Add y-axis property to widget

        :param axis: y-axis object
        """
        if (type(self).__name__ not in str(type(axis))):
            print('Chart type and axis type do not match.')
            return
        root_axes = self.root.find('y_axes')
        if root_axes is None:
            root_axes = SubElement(self.root, 'y_axes')
            root_axes.append(axis.root)
        else:
            root_axis = root_axes.findall('y_axis')
            if axis.root in root_axis:
                print('Y-Axis already exists.')
            else:
                root_axes.append(axis.root)

    def remove_y_axis(self, axis: object) -> None:
        """
        Removes y-axis from widget

        :param axis: y-axis object
        """
        root_axes = self.root.find('y_axes')
        if root_axes is None:
            print('This graph has no y-axes.')
        else:
            root_axis = root_axes.findall('y_axis')
            if axis.root in root_axis:
                root_axes.remove(axis.root)
            else:
                print('Y-Axis does not exist.')
            if not root_axis:
                self.root.remove(root_axes)   # removes hanging <y_axes/> tag from empty element

class _YAxis(object):   # single Y-Axis for Image widget
    def add_y_axis(self, axis: object) -> None:
        """
        Add y-axis property to widget

        :param axis: y-axis object
        """
        existing_axis = self.root.findall('y_axis')
        if existing_axis:
            print('A y-axis already exists. Replacing...')
            for item in existing_axis:
                self.root.remove(item)
            self.root.append(axis.root)
        else:
            self.root.append(axis.root)

    def remove_y_axis(self, axis: object) -> None:
        """
        Removes y-axis from widget

        :param axis: y-axis object
        """
        if self.root.findall('y_axis'):
            self.root.remove(axis.root)
        else:
            print('Y-Axis does not exist.')

class _XAxis(object):
    def add_x_axis(self, axis: object) -> None:
        """
        Add x-axis property to widget

        :param axis: x-axis object
        """
        existing_axis = self.root.findall('x_axis')
        if existing_axis:
            print('An x-axis already exists. Replacing...')
            for item in existing_axis:
                self.root.remove(item)
            self.root.append(axis.root)
        else:
            self.root.append(axis.root)

    def remove_x_axis(self, axis: object) -> None:
        """
        Removes x-axis from widget

        :param axis: x-axis object
        """
        if self.root.findall('x_axis'):
            self.root.remove(axis.root)
        else:
            print('X-Axis does not exist.')

class _Markers(object):
    def add_marker(self, marker: object) -> None:
        """
        Add marker property to widget

        :param marker: marker object
        """
        outer_marker = self.root.find('marker')
        if outer_marker is None:
            outer_marker = SubElement(self.root, 'marker')
            outer_marker.append(marker.root)
        else:
            inner_marker = outer_marker.findall('marker')
            if marker.root in inner_marker:
                print('Marker already exists.')
            else:
                outer_marker.append(marker.root)

    def remove_marker(self, marker: object) -> None:
        """
        Removes marker from widget

        :param marker: marker object
        """
        outer_marker = self.root.find('marker')
        if outer_marker is None:
            print('This graph has no markers.')
        else:
            inner_marker = outer_marker.findall('marker')
            if marker.root in inner_marker:
                outer_marker.remove(marker.root)
            else:
                print('Marker does not exist.')
            if not inner_marker:
                self.root.remove(outer_marker)

class _Interactive(object):
    def interactive(self, val: bool) -> None:
        """
        Set marker as interactive

        :param val: Interactive?
        """

        self._shared.boolean_property(self.root, 'interactive', val)

class _ItemsFromPV(object):
    def items_from_pv(self, val: bool) -> None:
        """
        Change Items from PV property to widget

        :param val: Use items from pv?
        """
        self._shared.boolean_property(self.root, 'items_from_pv', val)

class _ShowValueTip(object):
    def show_value_tip(self, val: bool) -> None:
        """
        Change Show Value Tip property to widget

        :param val: Show value tip?
        """
        self._shared.boolean_property(self.root, 'show_value_tip', val)

class _BarLength(object):
    def bar_length(self, val: Union[float, int]) -> None:
        """
        Change bar length property of widget

        :param val: Bar length value
        """
        self._shared.number_property(self.root, 'bar_length', val)

class _ShowValue(object):
    def show_value(self, val: bool) -> None:
        """
        Change show value property of widget

        :param val: Show value?
        """
        self._shared.boolean_property(self.root, 'show_value', val)

class _ShowLimits(object):
    def show_limits(self, val: bool) -> None:
        """
        Change show limits property of widget

        :param val: Show limits?
        """
        self._shared.boolean_property(self.root, 'show_limits', val)

class _LimitsFromPV(object):
    def limits_from_pv(self, val: bool) -> None:
        """
        Change limits from PV property for widget

        :param val: Use limits from PV?
        """
        self._shared.boolean_property(self.root, 'limits_from_pv', val)

class _MinMax(object):
    def minimum(self, val: Union[float, int]) -> None:
        """
        Add minimum value property to widget

        :param val: Minimum value
        """
        self._shared.number_property(self.root, 'minimum', val)

    def maximum(self, val: Union[float, int]) -> None:
        """
        Add maximum value property to widget

        :param val: Maximum value
        """
        self._shared.number_property(self.root, 'maximum', val)

class _NeedleColor(object):
    def needle_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Needle Color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'needle_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_needle_color(self, name: object) -> None:
        """
        Add named Needle Color property to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'needle_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _KnobColor(object):
    def knob_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Knob Color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'knob_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_knob_color(self, name: object) -> None:
        """
        Add named Knob Color property to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'knob_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _FillColor(object):
    def fill_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Fill Color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'fill_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_fill_color(self, name: object) -> None:
        """
        Add named Fill Color property to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'fill_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _EmptyColor(object):
    def empty_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add Empty Color property to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'empty_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_empty_color(self, name: object) -> None:
        """
        Add named Empty Color property to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'empty_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _ScaleVisible(object):
    def scale_visible(self, val: bool) -> None:
        """
        Change Scale Visible property on widget

        :param val: Is scale visible?
        """
        self._shared.boolean_property(self.root, 'scale_visible', val)

class _ShowLED(object):
    def show_led(self, val: bool) -> None:
        """
        Change Show LED property on widget

        :param val: Show LED?
        """
        self._shared.boolean_property(self.root, 'show_led', val)

class _Mode(object):
    def _add_mode(self, val):
        if not isinstance(val, self._shared.Mode):
            print('The component parameter must be of type Mode enum! Not: {}'.format(type(val)))
            return
        self._shared.generic_property(self.root, 'mode', val.value)

    def mode_toggle(self) -> None:
        """
        Change mode to Toggle
        """
        self._add_mode(self._shared.Mode.toggle)

    def mode_push(self) -> None:
        """
        Change mode to Push
        """
        self._add_mode(self._shared.Mode.push)

    def mode_push_inverted(self) -> None:
        """
        Change mode to Push Inverted
        """
        self._add_mode(self._shared.Mode.push_inverted)

class _Style(object):
    def _add_style(self, style):
        if not isinstance(style, self._shared.GroupStyle):
            print('Input type for param to group style must be of type GroupStyle. Not: {}'.format(type(style)))
            return
        self._shared.generic_property(self.root, 'style', style.value)

    def style_group_box(self) -> None:
        """
        Change widget style to Style Group Box
        """
        self._add_style(self._shared.GroupStyle.group_box)

    def style_title_bar(self) -> None:
        """
        Change widget style to Style Title Bar
        """
        self._add_style(self._shared.GroupStyle.title_bar)

    def style_line(self) -> None:
        """
        Change widget style to Style Line
        """
        self._add_style(self._shared.GroupStyle.line)

    def no_style(self) -> None:
        """
        Change widget style to No Style
        """
        self._add_style(self._shared.GroupStyle.none)

class _ResizeBehavior(object):
    def _add_resize_behavior(self, resize):
        if not isinstance(resize, self._shared.Resize):
            print('Resize behavior input must be of type enum Resize. Not: {}'.format(type(resize)))
        self._shared.generic_property(self.root, 'resize', resize.value)

    def no_resize(self) -> None:
        """
        Change resize behavior to No Resize
        """
        self._add_resize_behavior(self._shared.Resize.no_resize)

    def size_content_to_fit_widget(self) -> None:
        """
        Change resize behavior to Size Content to Fit Widget
        """
        self._add_resize_behavior(self._shared.Resize.size_content_to_fit_widget)

    def size_widget_to_match_content(self) -> None:
        """
        Change resize behavior to Size Widget to Match Content
        """
        self._add_resize_behavior(self._shared.Resize.size_widget_to_match_content)

    def stretch_content_to_fit_widget(self) -> None:
        """
        Change resize behavior to Stretch Content to Fit Widget
        """
        self._add_resize_behavior(self._shared.Resize.stretch_content_to_fit_widget)

    def crop_content(self) -> None:
        """
        Change resize behavior to Crop Content
        """
        self._add_resize_behavior(self._shared.Resize.crop_content)

class _GroupName(object):
    def group_name(self, val: str) -> None:
        """
        Add group name to widget

        :param val: Name of group
        """
        self._shared.generic_property(self.root, 'group_name', val)

class _Structure(object):
    """
    This class is used for Structure widgets to be a method to add widgets to the structure
    """
    def add_widget(self, elem: Union[list, object]) -> None:
        """
        Add widget to structure element (group, etc.)

        :param elem: <Phoebusgen.widget> Element or List of Elements to add to structure
        """
        if isinstance(elem, list):
            for e in elem:
                self.root.append(e.root)
        else:
            self.root.append(elem.root)

class _URL(object):
    def url(self, url: str) -> None:
        """
        Add URL string property for widget

        :param url: URL string
        """
        self._shared.generic_property(self.root, 'url', url)

class _ShowToolbar(object):
    def show_toolbar(self, val: bool) -> None:
        """
        Change option to show toolbar property option for the widget

        :param val: Show toolbar?
        """
        self._shared.boolean_property(self.root, 'show_toolbar', val)

class _ButtonsOnLeft(object):
    def buttons_on_left(self, val: bool) -> None:
        """
        Change buttons on the left property option for widget

        :param val: Have button on the left?
        """
        self._shared.boolean_property(self.root, 'buttons_on_left', val)

class _Increment(object):
    def increment(self, val: Union[float, int]) -> None:
        """
        Change increment property for widget

        :param val: Increment value
        """
        self._shared.number_property(self.root, 'increment', val)

class _FileComponent(object):
    def _add_file_component(self, val):
        if not isinstance(val, self._shared.FileComponent):
            print('The component parameter must be of type FileComponent enum! Not: {}'.format(type(val)))
            return
        self._shared.generic_property(self.root, 'component', val.value)

    def file_component_full_path(self) -> None:
        """
        Change file component to use full file path
        """
        self._add_file_component(self._shared.FileComponent.full_path)

    def file_component_directory(self) -> None:
        """
        Change file component to use directory name
        """
        self._add_file_component(self._shared.FileComponent.directory)

    def file_component_name_and_extension(self) -> None:
        """
        Change file component to only use file name and extension
        """
        self._add_file_component(self._shared.FileComponent.name_and_extension)

    def file_component_base_name(self) -> None:
        """
        Change file component to only use file base name
        """
        self._add_file_component(self._shared.FileComponent.base_name)

class _Editable(object):
    def editable(self, val: bool = True) -> None:
        """
        Change editable property on the widget. Default arg is True

        :param val: Is widget editable?
        """
        self._shared.boolean_property(self.root, 'editable', val)

class _SelectedColor(object):
    def selected_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add selected color property to widget

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'selected_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_selected_color(self, name: object) -> None:
        """
        Add predefined selected color name to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'selected_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _DeselectedColor(object):
    def deselected_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add deselected color property to widget

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'deselected_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

    def predefined_deselected_color(self, name: object) -> None:
        """
        Add predefined deselected color name to widget

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'deselected_color')
        self._shared.create_color_element(e, name, None, None, None, None)

class _SelectionValuePV(object):
    def selection_value_pv(self, val: str) -> None:
        """
        Add selection value PV to widget

        :param val: PV Name for selection value pv
        """
        self._shared.generic_property(self.root, 'selection_value_pv', val)

class _Points(object):
    def point(self, x: int, y: int) -> None:
        """
        Add point to points property of a widget

        :param x: X position of the point
        :param y: Y position of the point
        """
        if not isinstance(x, int) and not isinstance(x, float):
            print('Point X value must be an integer! Not: {}'.format(type(x)))
        elif not isinstance(y, int) and not isinstance(y, float):
            print('Point Y value must be an integer! Not: {}'.format(type(y)))
        else:
            root_points = self.root.find('points')
            if root_points is None:
                root_points = SubElement(self.root, 'points')
            point = SubElement(root_points, 'point')
            point.attrib['x'] = str(int(x))
            point.attrib['y'] = str(int(y))

class _Arrow(object):
    def arrow_length(self, length: int) -> None:
        """
        Change arrow length widget property

        :param length: Arrow length
        """
        self._shared.integer_property(self.root, 'arrow_length', length)

    def arrows_none(self) -> None:
        """
        Change arrow property to "None" for no arrows
        """
        self._shared.generic_property(self.root, 'arrows', self._shared.arrow_types['None'])

    def arrows_from(self) -> None:
        """
        Change arrow property to "From" for arrow direction
        """
        self._shared.generic_property(self.root, 'arrows', self._shared.arrow_types['From'])

    def arrows_to(self) -> None:
        """
        Change arrow property to "To" for arrow direction
        """
        self._shared.generic_property(self.root, 'arrows', self._shared.arrow_types['To'])

    def arrows_both(self) -> None:
        """
        Change arrow property to "Both"
        """
        self._shared.generic_property(self.root, 'arrows', self._shared.arrow_types['Both'])

class _LineStyle(object):
    def line_style_solid(self) -> None:
        """
        Change line style to solid
        """
        self._shared.integer_property(self.root, 'line_style', self._shared.LineStyle.solid.value)

    def line_style_dashed(self) -> None:
        """
        Change line style to dashed
        """
        self._shared.integer_property(self.root, 'line_style', self._shared.LineStyle.dashed.value)

    def line_style_dot(self) -> None:
        """
        Change line style to dot
        """
        self._shared.integer_property(self.root, 'line_style', self._shared.LineStyle.dot.value)

    def line_style_dash_dot(self) -> None:
        """
        Change line style to dash-dot
        """
        self._shared.integer_property(self.root, 'line_style', self._shared.LineStyle.dash_dot.value)

    def line_style_dash_dot_dot(self) -> None:
        """
        Change line style to dash-dot-dot
        """
        self._shared.integer_property(self.root, 'line_style', self._shared.LineStyle.dash_dot_dot.value)

class _Tabs(object):
    def tab(self, name: str) -> None:
        """
        Add tab to the Tabs widget

        :param name: Tab name, should be unique
        """
        root_tab = self.root.find('tabs')
        if root_tab is None:
            root_tab = SubElement(self.root, 'tabs')
        tab_elem = SubElement(root_tab, 'tab')
        name_elem = SubElement(tab_elem, 'name')
        name_elem.text = name
        SubElement(tab_elem, 'children')

    def add_widget(self, tab_name: str, elem: object) -> None:
        """
        Add widget to a specific tab with given tab_name

        :param tab_name: Name of the tab to add widget to
        :param elem: <Phoebusgen.widget> Widget to add to tab
        """
        root_tab = self.root.find('tabs')
        if root_tab is None:
            print('No tabs widget available!')
        else:
            tab_elem_list = root_tab.findall('tab')
            foundIt = False
            for tab in tab_elem_list:
                if tab.find('name').text == tab_name:
                    tab.find('children').append(elem.root)
                    foundIt = True
            if not foundIt:
                print('No tab with the given name: {}'.format(tab_name))

class _NavTabs(object):
    def tab(self, name: str, file_name: str, group_name: str, macros: dict = None) -> None:
        """
        Add tab to the Navigation Tab widget. Nav tabs use bob files. Macro arg is optional

        :param name: Tab name
        :param file_name: .bob file name
        :param group_name: Tab group name
        :param macros: Dictionary of macros. key=macro name and value=macro value
        """
        root_tab = self.root.find('tabs')
        if root_tab is None:
            root_tab = SubElement(self.root, 'tabs')
        tab_elem = SubElement(root_tab, 'tab')
        self._shared.generic_property(tab_elem, 'name', name)
        self._shared.generic_property(tab_elem, 'file', file_name)
        self._shared.generic_property(tab_elem, 'group_name', group_name)
        SubElement(tab_elem, 'macros')
        if macros is not None:
            if not isinstance(macros, dict):
                print('macros parameter must be of type dict, not: {}'.format(type(macros)))
            else:
                for key, val in macros.items():
                    self._shared.add_macro(key, val, tab_elem)

class _ActiveTab(object):
    def active_tab(self, tab_num: int) -> None:
        """
        Select active tab number for the widget

        :param tab_num: Tab number to be active on page open
        """
        self._shared.integer_property(self.root, 'active_tab', tab_num)

class _TabHeight(object):
    def tab_height(self, height: int) -> None:
        """
        Add tab height property to widget

        :param height: Height value for tabs
        """
        self._shared.integer_property(self.root, 'tab_height', height)

class _TabWidth(object):
    def tab_width(self, width: int) -> None:
        """
        Add tab width property to widget

        :param width: Width value for tabs
        """
        self._shared.integer_property(self.root, 'tab_width', width)

class _TabSpacing(object):
    def tab_spacing(self, spacing: int) -> None:
        """
        Add tab spacing property to widget

        :param spacing: Tab spacing value
        """
        self._shared.integer_property(self.root, 'tab_spacing', spacing)

class _Direction(object):
    def tab_direction_horizontal(self) -> None:
        """
        Change tab direction property to horizontal for widget
        """
        self._shared.integer_property(self.root, 'direction', 0)

    def tab_direction_vertical(self) -> None:
        """
        Change tab direction property to vertical for widget
        """
        self._shared.integer_property(self.root, 'direction', 1)

class _NumBits(object):
    def num_bits(self, number_of_bits: int) -> None:
        """
        Add number of bits property to widget

        :param number_of_bits: Number of bits
        """
        self._shared.integer_property(self.root, 'numBits', number_of_bits)

class _StartBit(object):
    def start_bit(self, start_bit: int) -> None:
        """
        Adding start bit property to widget

        :param start_bit: Start bit
        """
        self._shared.integer_property(self.root, 'startBit', start_bit)

class _ReverseBits(object):
    def reverse_bits(self, reverse_bits: bool = True) -> None:
        """
        Add reverse bits option on widget. Default arg value is True

        :param reverse_bits: Reverse bits?
        """
        self._shared.boolean_property(self.root, 'bitReverse', reverse_bits)

class _Labels(object):
    def labels(self, label_list_or_name: Union[list, str]) -> None:
        """
        Add label property to widget

        :param label_list_or_name: List of label strings or a single label string
        """
        if not isinstance(label_list_or_name, list) and not isinstance(label_list_or_name, str):
            print('Parameter to labels must be a list of strings or a single string, not: {}'.format(type(label_list_or_name)))
        else:
            root_label_tag = self.root.find('labels')
            if root_label_tag is None:
                root_label_tag = SubElement(self.root, 'labels')
            if isinstance(label_list_or_name, list):
                for label in label_list_or_name:
                    label_elem = SubElement(root_label_tag, 'text')
                    label_elem.text = label
            else:
                self._shared.generic_property(root_label_tag, 'text', label_list_or_name)

class _ArrayIndex(object):
    def array_index(self, index: int) -> None:
        """
        Add array index to widget

        :param index: Array index
        """
        self._shared.integer_property(self.root, 'array_index', index)

class _Symbols(object):
    def symbols(self, symbol_list_or_string: Union[list, str]) -> None:
        """
        Add symbol to widget. Symbols can be file name or text symbol

        :param symbol_list_or_string: List of strings/file names or single string/file name
        """
        if not isinstance(symbol_list_or_string, list) and not isinstance(symbol_list_or_string, str):
            print('Parameter to labels must be a list of strings or a single string, not: {}'.format(type(symbol_list_or_string)))
        else:
            root_label_tag = self.root.find('symbols')
            if root_label_tag is None:
                root_label_tag = SubElement(self.root, 'symbols')
            if isinstance(symbol_list_or_string, list):
                for label in symbol_list_or_string:
                    label_elem = SubElement(root_label_tag, 'symbol')
                    label_elem.text = label
            else:
                self._shared.list_property(root_label_tag, 'symbol', symbol_list_or_string)

class _InitialIndex(object):
    def initial_index(self, index: int) -> None:
        """
        Add initial index to widget

        :param index: Index
        """
        self._shared.integer_property(self.root, 'initial_index', index)

class _ShowIndex(object):
    def show_index(self, show: bool = True) -> None:
        """
        Add show index option on widget. Default arg value is True

        :param show: Show index?
        """
        self._shared.boolean_property(self.root, 'show_index', show)

class _PreserveRatio(object):
    def preserve_ratio(self, preserve_ratio: bool = True) -> None:
        """
        Add preserve ratio option on widget. Default arg value is True

        :param preserve_ratio: Preserve ratio?
        """
        self._shared.boolean_property(self.root, 'preserve_ratio', preserve_ratio)

class _ShowScale(object):
    def show_scale(self, show: bool = True) -> None:
        """
        Add show scale option on widget. Default arg value is True

        :param show: Show scale?
        """
        self._shared.boolean_property(self.root, 'show_scale', show)

class _ShowMinorTicks(object):
    def show_minor_ticks(self, show: bool = True) -> None:
        """
        Add show minor ticks option on widget. Default arg value is True

        :param show: Show minor ticks?
        """
        self._shared.boolean_property(self.root, 'show_minor_ticks', show)

class _MajorTicksPixelDist(object):
    def major_ticks_pixel_dist(self, dist: int) -> None:
        """
        Add major ticks pixel distribution value to widget

        :param dist: Pixel distribution
        """
        self._shared.integer_property(self.root, 'major_tick_step_hint', dist)

class _ScaleFormat(object):
    def scale_format(self, format_string: str) -> None:
        """
        Add scale format string to widget

        :param format_string: Formatting string, ex: #.##
        """
        self._shared.generic_property(self.root, 'scale_format', format_string)

class _LevelsAndShow(object):
    def level_hihi(self, level: Union[float, int]) -> None:
        """
        Add HiHi level on widget

        :param level: HiHi level
        """
        self._shared.number_property(self.root, 'level_hihi', level)

    def level_high(self, level: Union[float, int]) -> None:
        """
        Add High level on widget

        :param level: High level
        """
        self._shared.number_property(self.root, 'level_high', level)

    def level_low(self, level: Union[float, int]) -> None:
        """
        Add Low level on widget

        :param level: Low level
        """
        self._shared.number_property(self.root, 'level_low', level)

    def level_lolo(self, level: Union[float, int]) -> None:
        """
        Add LoLo level on widget

        :param level: LoLo level
        """
        self._shared.number_property(self.root, 'level_lolo', level)

    def show_hihi(self, show: bool = True) -> None:
        """
        Add ShowHiHi option on widget. Default arg is True

        :param show: Show HiHi?
        """
        self._shared.boolean_property(self.root, 'show_hihi', show)

    def show_high(self, show: bool = True) -> None:
        """
        Add ShowHigh option on widget. Default arg is True

        :param show: Show High?
        """
        self._shared.boolean_property(self.root, 'show_high', show)

    def show_low(self, show: bool = True) -> None:
        """
        Add ShowLow option on widget. Default arg is True

        :param show: Show Low?
        """
        self._shared.boolean_property(self.root, 'show_low', show)

    def show_lolo(self, show: bool = True) -> None:
        """
        Add ShowLoLo option on widget. Default arg is True

        :param show: Show LoLo?
        """
        self._shared.boolean_property(self.root, 'show_lolo', show)

class _States(object):
    def _add_state(self, state_value, label, name, red, green, blue, alpha):
        root_states = self.root.find('states')
        if root_states is None:
            root_states = SubElement(self.root, 'states')
        root_state = SubElement(root_states, 'state')
        self._shared.integer_property(root_state, 'value', state_value)
        self._shared.generic_property(root_state, 'label', label)
        color_elem = SubElement(root_state, 'color')
        self._shared.create_color_element(color_elem, name, red, green, blue, alpha, False)

    def state(self, state_value: int, label: str, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add state color to widget with RGB values

        :param state_value: Integer value of the state
        :param label: State label
        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        self._add_state(state_value, label, None, red, green, blue, alpha)

    def state_predefined_color(self, state_value: int, label: str, color_name: object) -> None:
        """
        Add state color to widget with a named color

        :param state_value: Integer value of the state
        :param label: State label
        :param color_name: <phoebusgen.colors> Predefined color name
        """
        self._add_state(state_value, label, color_name, None, None, None, None)

class _Fallback(object):
    def fallback_label(self, label: str) -> None:
        """
        Add fallback label to widget

        :param label: label
        """
        self._shared.generic_property(self.root, 'fallback_label', label)

    def predefined_fallback_color(self, name: object) -> None:
        """
        Add fallback color to widget with a named color

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'fallback_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def fallback_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add fallback color to widget with RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'fallback_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _SelectRows(object):
    def select_rows(self, select_rows: bool = True) -> None:
        """
        Add row selection mode to widget. Default arg is True

        :param select_rows: Are rows selectable?
        """
        self._shared.boolean_property(self.root, 'row_selection_mode', select_rows)

class _SelectionPV(object):
    def selection_pv(self, name: str) -> None:
        """
        Add selection PV to widget

        :param name: PV Name
        """
        self._shared.generic_property(self.root, 'selection_pv', name)

class _Columns(object):
    def column(self, name: str, width: int, editable: bool, options: Union[list, str]) -> None:
        """
        Add column property to widget

        :param name: Name of column
        :param width: Column width
        :param editable: Is column editable?
        :param options: List of strings or single string
        """
        columns_root = self.root.find('columns')
        if columns_root is None:
            columns_root = SubElement(self.root, 'columns')
        column_elem = SubElement(columns_root, 'column')
        self._shared.generic_property(column_elem, 'name', name)
        self._shared.integer_property(column_elem, 'width', width)
        self._shared.boolean_property(column_elem, 'editable', editable)
        if options is None:
            return
        elif not isinstance(options, list) and not isinstance(options, str):
            print('options parameter must be a list of strings or a single string')
            return
        options_root = column_elem.find('options')
        if options_root is None:
            options_root = SubElement(column_elem, 'options')
        if isinstance(options, list):
            for opt in options:
                option_elem = SubElement(options_root, 'option')
                option_elem.text = opt
        else:
            self._shared.generic_property(options_root, 'option', options)

class _Title(object):
    def title(self, title: str) -> None:
        """
        Add title to widget

        :param title: Title
        """
        self._shared.generic_property(self.root, 'title', title)

class _AutoScale(object):
    def auto_scale(self, auto_scale: bool) -> None:
        """
        Add auto scale property to widget

        :param auto_scale: Auto scale image?
        """
        self._shared.boolean_property(self.root, 'autoscale', auto_scale)

class _DataHeightAndWidth(object):
    def data_height(self, height: int) -> None:
        """
        Add data height property to widget

        :param height: Data height
        """
        self._shared.integer_property(self.root, 'data_height', height)

    def data_width(self, width: int) -> None:
        """
        Add data width property to widget

        :param width: Data width
        """
        self._shared.integer_property(self.root, 'data_width', width)

class _UnsignedData(object):
    def unsigned_data(self, unsigned: bool = True) -> None:
        """
        Add unsigned data property to widget. Default arg is True

        :param unsigned: Is data unsigned?
        """
        self._shared.boolean_property(self.root, 'unsigned', unsigned)

class _LogScale(object):
    def log_scale(self, log_scale: bool = True) -> None:
        """
        Add log scale property to widget. Default arg is True

        :param log_scale: Use log scale?
        """
        self._shared.boolean_property(self.root, 'log_scale', log_scale)

class _ShowLegend(object):
    def show_legend(self, show_legend: bool = True) -> None:
        """
        Add show legend property to widget. Default arg is True

        :param show_legend: Show legend?
        """
        self._shared.boolean_property(self.root, 'show_legend', show_legend)

class _ShowGrid(object):
    def show_grid(self, show_grid: bool = True) -> None:
        """
        Add show grid property to widget. Default arg is True

        :param show_grid: Show grid?
        """
        self._shared.boolean_property(self.root, 'show_grid', show_grid)

class _TimeRange(object):
    def time_range(self, time_range: str) -> None:
        """
        Add time range of plot to widget

        :param time_range: Time range of plot, i.e. "5 minutes", "1 hour", etc
        """
        self._shared.generic_property(self.root, 'time_range', time_range)

class _GridColor(object):
    def predefined_grid_color(self, name: object) -> None:
        """
        Add named grid color property

        :param name: <phoebusgen.colors> Predefined color name
        """
        e = self._shared.create_element(self.root, 'grid_color')
        self._shared.create_color_element(e, name, None, None, None, None)

    def grid_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None:
        """
        Add grid color property using RGB values

        :param red: 0-255
        :param green: 0-255
        :param blue: 0-255
        :param alpha: 0-255. Default is 255
        """
        e = self._shared.create_element(self.root, 'grid_color')
        self._shared.create_color_element(e, None, red, green, blue, alpha)

class _Cursor(object):
    def cursor_info_pv(self, info_pv_name: str) -> None:
        """
        Add cursor info PV to widget

        :param info_pv_name: PV Name
        """
        self._shared.generic_property(self.root, 'cursor_info_pv', info_pv_name)

    def cursor_x_pv(self, x_pv_name: str) -> None:
        """
        Add cursor X PV to widget

        :param x_pv_name: PV Name
        """
        self._shared.generic_property(self.root, 'x_pv', x_pv_name)

    def cursor_y_pv(self, y_pv_name: str) -> None:
        """
        Add cursor Y PV to widget

        :param y_pv_name: PV Name
        """
        self._shared.generic_property(self.root, 'y_pv', y_pv_name)

    def cursor_crosshair(self, cursor_crosshair: bool = True) -> None:
        """
        Toggle widget cursor crosshair property. Default arg is True

        :param cursor_crosshair: Is widget transparent?
        """
        self._shared.boolean_property(self.root, 'cursor_crosshair', cursor_crosshair)

class _Interpolation(object):
    def _add_interpolation(self, val):
        if not isinstance(val, self._shared.Interpolation):
            print('The component parameter must be of type Interpolation enum! Not: {}'.format(type(val)))
            return
        self._shared.generic_property(self.root, 'interpolation', val.value)

    def interpolation_none(self) -> None:
        """
        Change interpolation to None
        """
        self._add_interpolation(self._shared.Interpolation.none)

    def interpolation_interpolate(self) -> None:
        """
        Change interpolation to Interpolate
        """
        self._add_interpolation(self._shared.Interpolation.interpolate)

    def interpolation_automatic(self) -> None:
        """
        Change interpolation to Automatic
        """
        self._add_interpolation(self._shared.Interpolation.automatic)

class _ColorMode(object):
    def _add_color_mode(self, val):
        if not isinstance(val, self._shared.ColorMode):
            print('The component parameter must be of type ColorMode enum! Not: {}'.format(type(val)))
            return
        self._shared.generic_property(self.root, 'color_mode', val.value)

    def color_mode_CUSTOM(self) -> None:
        """
        Change color mode to CUSTOM
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_CUSTOM)

    def color_mode_MONO(self) -> None:
        """
        Change color mode to MONO
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_MONO)

    def color_mode_BAYER(self) -> None:
        """
        Change color mode to BAYER
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_BAYER)

    def color_mode_RGB1(self) -> None:
        """
        Change color mode to RGB1
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_RGB1)

    def color_mode_RGB2(self) -> None:
        """
        Change color mode to RGB2
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_RGB2)

    def color_mode_RGB3(self) -> None:
        """
        Change color mode to RGB3
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_RGB3)

    def color_mode_YUV444(self) -> None:
        """
        Change color mode to YUV444
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_YUV444)

    def color_mode_YUV422(self) -> None:
        """
        Change color mode to YUV422
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_YUV422)

    def color_mode_YUV411(self) -> None:
        """
        Change color mode to YUV411
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_YUV411)

    def color_mode_3BYTE_BGR(self) -> None:
        """
        Change color mode to 3BYTE_BGR
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_3BYTE_BGR)

    def color_mode_4BYTE_ABGR(self) -> None:
        """
        Change color mode to 4BYTE_ABGR
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_4BYTE_ABGR)

    def color_mode_4BYTE_ABGR_PRE(self) -> None:
        """
        Change color mode to 4BYTE_ABGR_PRE
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_4BYTE_ABGR_PRE)

    def color_mode_BYTE_BINARY(self) -> None:
        """
        Change color mode to BYTE_BINARY
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_BYTE_BINARY)

    def color_mode_BYTE_GRAY(self) -> None:
        """
        Change color mode to BYTE_GRAY
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_BYTE_GRAY)

    def color_mode_BYTE_INDEXED(self) -> None:
        """
        Change color mode to BYTE_INDEXED
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_BYTE_INDEXED)

    def color_mode_INT_ARGB(self) -> None:
        """
        Change color mode to INT_ARGB
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_INT_ARGB)

    def color_mode_INT_ARGB_PRE(self) -> None:
        """
        Change color mode to INT_ARGB_PRE
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_INT_ARGB_PRE)

    def color_mode_INT_BGR(self) -> None:
        """
        Change color mode to INT_BGR
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_INT_BGR)

    def color_mode_INT_RGB(self) -> None:
        """
        Change color mode to INT_RGB
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_INT_RGB)

    def color_mode_USHORT_555_RGB(self) -> None:
        """
        Change color mode to USHORT_555_RGB
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_USHORT_555_RGB)

    def color_mode_USHORT_565_RGB(self) -> None:
        """
        Change color mode to USHORT_565_RGB
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_USHORT_565_RGB)

    def color_mode_USHORT_GRAY(self) -> None:
        """
        Change color mode to USHORT_GRAY
        """
        self._add_color_mode(self._shared.ColorMode.TYPE_USHORT_GRAY)

class _RegionsOfInterest(object):
    def add_roi(self, roi: object) -> None:
        """
        Add ROI to list of ROIs for widget

        :param roi: ROI object
        """
        rois_tag = self.root.find('rois')
        if rois_tag is None:
            rois_tag = SubElement(self.root, 'rois')
            rois_tag.append(roi.root)
        else:
            roi_tag = rois_tag.findall('roi')
            if roi.root in roi_tag:
                print('ROI already exists.')
            else:
                rois_tag.append(roi.root)

    def remove_roi(self, roi: object) -> None:
        """
        Removes ROI from widget

        :param roi: ROI object
        """
        rois_tag = self.root.find('rois')
        if rois_tag is None:
            print('This graph has no ROIs.')
        else:
            roi_tag = rois_tag.findall('roi')
            if roi.root in roi_tag:
                rois_tag.remove(roi.root)
            else:
                print('ROI does not exist.')
            if not roi_tag:
                self.root.remove(rois_tag)

class _ColorBarSize(object):
    def bar_size(self, val: int) -> None:
        """
        Add size value to color bar

        :param val: Bar size value
        """
        self._shared.integer_property(self.root, 'bar_size', val)

class _ColorBar(object):
    def add_color_bar(self, bar: object) -> None:
        """
        Add color bar property to widget

        :param bar: Color bar object
        """
        existing_color_bar = self.root.findall('color_bar')
        if existing_color_bar:
            print('A color bar already exists. Replacing...')
            for item in existing_color_bar:
                self.root.remove(item)
            self.root.append(bar.root)
        else:
            self.root.append(bar.root)

    def remove_color_bar(self, bar: object) -> None:
        """
        Removes color bar from widget

        :param bar: Color bar object
        """
        if self.root.findall('color_bar'):
            self.root.remove(bar.root)
        else:
            print('Color bar does not exist.')

class _ColorMap(object):
    def add_color_map(self, color: object) -> None:
        """
        Adds custom color map

        :param color: Color map object
        """
        existing = self.root.find('color_map')
        if existing is None:
            existing = SubElement(self.root, 'color_map')
            existing.append(color.root)
        else:
            section = existing.findall('section')
            if color.root in section:
                print('Color map section already exists.')
            else:
                name = existing.findall('name')
                if name:
                    self._remove_all(existing, name)    # removes predefined map if exists
                existing.append(color.root)
                colors = existing.findall('section')
                existing = self._reorder(existing, colors) # puts in descending order by value

    def predefined_color_map(self, name: str) -> None:
        """
        Adds color map from one of the predefined enums

        :param name: Name of predefined color map
        """
        if (name not in (predef.value for predef in self._shared.ColorMap)):
            print('Color map name is undefined.')
            return
        existing = self.root.find('color_map')
        if existing is None:
            existing = SubElement(self.root, 'color_map')
        else:
            section = existing.findall('section')
            if section:
                self._remove_all(existing, section)     # removes custom map if exists
        self._shared.generic_property(existing, 'name', name)   # will replace with another predefined -- no del func

    def remove_color_map(self, color: object) -> None:
        """
        Removes custom color map color

        :param color: Color map object
        """
        existing = self.root.find('color_map')
        if existing is None:
            print('This graph has no color map.')
        else:
            section = existing.findall('section')
            if color.root in section:
                existing.remove(color.root)
                self._check_value(existing.findall('section'))  # ensures that first is 0 and last is 255 after deletion
            else:
                print('Color map section does not exist.')

            section = existing.findall('section')
            if not section:
                self.root.remove(existing)

    def _remove_all(self, root, color_map):
        for item in color_map:
            root.remove(item)

    def _reorder(self, root, color_map):
        colors = sorted(color_map, key=lambda color: (color.attrib['value']))
        self._remove_all(root, color_map)
        color_map[:] = colors
        for item in colors:
            root.append(item)
        return root

    def _check_value(self, color_map):    # ensures that the starting and ending values are correct
        if color_map:
            if len(color_map) > 1:
                color_map[0].attrib['value'] = '0'
                color_map[len(color_map) - 1].attrib['value'] = '255'
            else:
                print('Color Map must have at least 2 colors.')
