class MouseWheel(object):
    ''' This class bind scrolling events to a scroll object.
    '''
    def __init__(self, root, factor=1):
        global os
        self.activeArea = None
        if type(factor) == int:
            self.factor = factor
        else:
            raise Exception("Factor must be an integer.")

        if os == "Linux":
            root.bind_all('<4>', self.onMouseWheel,  add='+')
            root.bind_all('<5>', self.onMouseWheel,  add='+')
        else:
            # Windows and MacOS
            root.bind_all("<MouseWheel>", self.onMouseWheel,  add='+')

        self.root = root

    def onMouseWheel(self, event):
        if self.activeArea:
            self.activeArea.onMouseWheel(event)

    def mouseWheel_bind(self, widget):
        self.activeArea = widget

    def mouseWheel_unbind(self):
        self.activeArea = None

    @staticmethod
    def build_function_onMouseWheel(widget, orient, scrollbar, factor=1):
        view_command = getattr(widget, orient+'view')
        if os == 'Linux':
            def onMouseWheel(event):
                # scroll moves only when it is not disables

                if (len(scrollbar.state()) == 0 or
                        scrollbar.state()[0] != 'disabled'):
                    if event.num == 4:
                        view_command("scroll", (-1)*factor, "units")
                    elif event.num == 5:
                        view_command("scroll", factor, "units")

        elif os == 'Windows':
            def onMouseWheel(event):
                if (len(scrollbar.state()) == 0 or
                        scrollbar.state()[0] != 'disabled'):
                    view_command("scroll", (-1)*int((event.delta/120)*factor),
                                 "units")

        elif os == 'Darwin':
            def onMouseWheel(event):
                if (len(scrollbar.state()) == 0 or
                        scrollbar.state()[0] != 'disabled'):
                    view_command("scroll", event.delta, "units")

        return onMouseWheel

    def add_scrolling(self, scrollingArea, xscrollbar=None, yscrollbar=None):
        scrollingArea.bind('<Enter>',
                           lambda event: self.mouseWheel_bind(scrollingArea))
        scrollingArea.bind('<Leave>',
                           lambda event: self.mouseWheel_unbind())
        scrollingArea.bind('<Configure>',
                           lambda event: on_canvas_resize(scrollingArea))

        if xscrollbar and not hasattr(xscrollbar, 'onMouseWheel'):
                setattr(xscrollbar, 'onMouseWheel',
                        self.build_function_onMouseWheel(scrollingArea, 'x',
                        xscrollbar, self.factor))

        if yscrollbar and not hasattr(yscrollbar, 'onMouseWheel'):
                setattr(yscrollbar, 'onMouseWheel',
                        self.build_function_onMouseWheel(scrollingArea, 'y',
                        yscrollbar, self.factor))

        active_scrollbar_on_mouse_wheel = yscrollbar or xscrollbar
        if active_scrollbar_on_mouse_wheel:
            setattr(scrollingArea, 'onMouseWheel',
                    active_scrollbar_on_mouse_wheel.onMouseWheel)

        for scrollbar in (xscrollbar, yscrollbar):
            if scrollbar:
                scrollbar.bind(
                    '<Enter>', lambda event,
                    scrollbar=scrollbar: self.mouseWheel_bind(scrollbar))
                scrollbar.bind('<Leave>',
                               lambda event: self.mouseWheel_unbind()) def is_valid_coeff(coeff):
    ''' Checks if given coefficient is valid. Valid coefficient is positive
        floating point or integer number.

        Args:
            coeff (double): data coefficient.

        Returns:
            int: 1, if coefficient is valid, 0 if coefficient is zero,
                -1, if coefficient is invalid.
    '''
    try:
        coeff = float(coeff)
        if coeff < 0:
            return NOT_VALID_COEFF
        elif coeff == 0:
            return WARNING_COEFF
        else:
            return VALID_COEFF
    except ValueError:
        return NOT_VALID_COEFF NOT_VALID_COEFF = -1 VALID_COEFF = 1 WARNING_COEFF = 0 EMPTY_COEFF = -2 CELL_DESTROY = -3 CHANGE_CATEGORY_NAME = -4 INPUT_OBSERVER = 1 OUTPUT_OBSERVER = 2 def on_canvas_resize(canvas):
    ''' This function updates scroll region of the canvas.
        It should be called on canvas resize.

        Args:
            canvas (Canvas): canvas
    '''
    canvas.update_idletasks()
    yscroll = 0
    xscroll = 0
    if canvas.bbox(ALL)[2] > canvas.winfo_width():
        yscroll = canvas.bbox(ALL)[2]
    if canvas.bbox(ALL)[3] > canvas.winfo_height():
        xscroll = canvas.bbox(ALL)[3]
    canvas['scrollregion'] = (0, 0, yscroll, xscroll) def validate_category_name(name, category_index, current_categories):
    ''' Checks if given category name is valid. Name is valid if it is not
        a number, if it does not contain semicolon and if it is not duplicated.

        Args:
            name (str): category name.
            category_index (int): index of this category in the list of
                current categories.
            current_categories (list of str): list of current categories.

        Returns:
            str: given category name if this name is valid, empty
                string otherwise.

        Example:
            >>> validate_category_name('I1n', 0, ['I1n', 'I2', 'O2])
            >>> 'I1n'
            >>> validate_category_name('I1n;', 0, ['I1n;', 'I2', 'O2])
            >>> ''
            >>> validate_category_name('1.2', 0, ['1.2', 'I2', 'O2])
            >>> ''
            >>> validate_category_name('I1n', 0, ['I1n', 'I1n', 'O2])
            >>> ''
    '''
    try:
        float(name)
    except ValueError:
        if ';' in name:
            return ''
        for index, category in enumerate(current_categories):
            if index != category_index and category == name:
                return ''
        return name
    else:
        return '' def calculate_nb_pages(nb_data_rows, nb_table_rows):
    ''' Calculates number of pages given number of data rows and number of rows
        in the table.

        Note:
            first row is reserved for categories.

        Args:
            nb_data_rows (int): number of data rows.

                Warning:
                    It must be a positive number. Otherwise the function
                    will return incorrect value.

            nb_table_rows (int): number of rows in the table.

                Warning:
                    It must be a positive number. Otherwise the function
                    will return incorrect value.

        Returns:
            int: number of pages

        Example:
            >>> calculate_nb_pages(100, 10)
            >>> 12
            >>> calculate_nb_pages(25, 20)
            >>> 2
            >>> calculate_nb_pages(0, 20)
            >>> 1
            >>> calculate_nb_pages(10, 25):
            >>> 1

    '''
    if nb_table_rows:
        base = int(nb_data_rows / (nb_table_rows - 1))
        if nb_data_rows % (nb_table_rows - 1) != 0:
            base += 1
        return max(base, 1)
    return 0 class StyledCanvas(Canvas):
    ''' Implements Canvas object with a custom background colour.

        Args:
            parent (Tk object): parent of this widget.
    '''
    def __init__(self, parent, *args, **kw):
        super().__init__(parent, *args, **kw)
        self.configure(background=bg_color) def convert_to_dictionary(data, check_value=fake_fnc):
    ''' Converts given list of data to a dictionary.

        Args:
            data (list of str or double): list where the first element
                is DMU name, all other elements are coefficients.
            check_value (func, optional): function that is called on each
                element of the data list (except the first one) before
                adding the coefficient to a dictionary. Defaults to
                fake_fnc.

        Returns:
            tuple of dict of str to list of double, bool:
                tuple with the following values:

                * dictionary that  maps DMU name to a list with coefficients.
                * true if there are the same DMU names, False otherwise.
    '''
    coefficients = OrderedDict()
    has_same_dmus = False
    for values in data:
        if values[0] in coefficients.keys():
            has_same_dmus = True
        coefficients[values[0]] = [float(val) for count, val in
                                   enumerate(values[1:])
                                   if check_value(count)]

    return coefficients, has_same_dmus
from tkinter import S, N, E, W, END, VERTICAL, HORIZONTAL, ALL
from tkinter import IntVar, DISABLED, StringVar, NORMAL
from tkinter.ttk import Frame, Entry, Scrollbar, Checkbutton
from pyDEA.core.gui_modules.scrollable_frame_gui import MouseWheel
from pyDEA.core.utils.dea_utils import is_valid_coeff, NOT_VALID_COEFF, VALID_COEFF
from pyDEA.core.utils.dea_utils import WARNING_COEFF, EMPTY_COEFF, CELL_DESTROY
from pyDEA.core.utils.dea_utils import CHANGE_CATEGORY_NAME, INPUT_OBSERVER
from pyDEA.core.utils.dea_utils import OUTPUT_OBSERVER, on_canvas_resize
from pyDEA.core.utils.dea_utils import validate_category_name, calculate_nb_pages
from pyDEA.core.gui_modules.custom_canvas_gui import StyledCanvas
from pyDEA.core.data_processing.read_data import convert_to_dictionary
        cell.data_column = -1


class ObserverCheckbutton(Checkbutton):
    ''' This class implements Checkbutton for choosing input/output categories.

        Attributes:
            var (IntVar): variable that is set to 1 when Checkbutton is
                selected, to 0 otherwise.
            opposite_var (IntVar): variable of the other Checkbutton that
                must deselected if this Checkbutton is selected.
            parent (Tk object): frame that holds this Checkbutton.

                Warning:
                    it is important for the parent to be gridded in the
                    same column
                    as the entire column of table entries is gridded, because
                    this class uses parent grid column index to determine
                    the column where the category name can be read from.

            category_frame (CategoriesCheckBox): frame that displays selected
                input or output categories.

                Note:
                    if this Checkbutton is used to select input categories,
                    category_frame must be CategoriesCheckBox object that
                    displays selected input categories.
                    if this Checkbutton is used to select output categories,
                    category_frame  must be CategoriesCheckBox object that
                    displays selected output categories.

            opposite_category_frame (CategoriesCheckBox): frame that displays
                selected input or output categories. If category_frame
                displays input categories, then opposite_category_frame
                must display output categories, and vice versa.
            current_categories (list of str): list of categories. This class
                might modify this list by removing invalid categories and
                adding the valid ones.
            cells (list of list of SelfValidatingEntry): all entry widgets
                collected in list.
            data (list of list of str or float): input data.
            observer_type (int): describes type of the observer, for possible
                values see dea_utils.
            change_category_name (callable function): this function is
                called when name of a category was changed.
            combobox_text_var (StringVar): variable of the combobox used for
                selecting categorical category.

        Arguments are the same as attributes.
    '''
    def __init__(self, parent, var, opposite_var, category_frame,
                 opposite_category_frame,
                 current_categories, cells,
                 observer_type, change_category_name, data,
                 combobox_text_var, *args, **kw):
        Checkbutton.__init__(self, parent, variable=var,
                             command=self._process, *args, **kw)
        self.var = var
        self.opposite_var = opposite_var
        self.parent = parent
        self.category_frame = category_frame
        self.opposite_category_frame = opposite_category_frame
        self.current_categories = current_categories
        self.cells = cells
        self.data = data
        self.observer_type = observer_type
        self.change_category_name = change_category_name
        self.combobox_text_var = combobox_text_var

    def _process(self):
        ''' This method is called when user clicks on Checkbutton.
            Makes sure that the same category can be only input or only
            output, but not both, and that selected category cannot also
            be selected as a categorical category.
        '''
        category_name = self.get_category()
        if self.var.get() == 1:
            self.opposite_var.set(0)

            if category_name:
                self.category_frame.add_category(category_name)
                self.opposite_category_frame.remove_category(category_name)
            if category_name == self.combobox_text_var.get():
                self.combobox_text_var.set('')
        elif category_name:
            self.category_frame.remove_category(category_name)

    def deselect(self):
        ''' Deselects Checkbutton.

            Note:
                method _process() is not called in this case.
        '''
        self.var.set(0)

    def select(self):
        ''' Selects Checkbutton.

            Note:
                method _process() is not called in this case.
        '''
        self.var.set(1)

    def change_state_if_needed(self, entry, entry_state, row, col):
        ''' Changes state of Checkbutton when data or categories were modified.
            Also modifies current_categories if needed.
            This widget becomes disabled if invalid category name value or input
            data value were provided by user.

            Args:
                entry (SelfValidatingEntry): Entry widget whose content was
                    modified.
                entry_state (int): state of the Entry widget after content
                    modification, for possible values see dea_utils module.
                row (int): row index of entry widget. It is the real grid value,
                    we need to subtract 2 to get internal index.
                col (int): column index of entry widget. It is the real grid
                    value, we need to subtract 2 to get internal index.

        '''

        if entry_state == CHANGE_CATEGORY_NAME: