Example #1
0
class Node:
    def __init__(self, x: int, y: int, width: int):
        self.x = x
        self.y = y
        self.width = width

        self.__neighbors = OrderedSet()
        self.__conn_ins = []
        self.__edge_cost = {}

    def add_edge(self, node: "Node", delay: int = 0,
                 force_connect: bool = False):
        if not force_connect:
            assert self.width == node.width
        if node not in self.__neighbors:
            self.__neighbors.add(node)
            node.__conn_ins.append(self)
            self.__edge_cost[node] = delay

    def remove_edge(self, node: "Node"):
        if node in self.__neighbors:
            self.__edge_cost.pop(node)
            self.__neighbors.remove(node)

            # remove the incoming connections as well
            node.__conn_ins.remove(self)

    def get_edge_cost(self, node: "Node") -> int:
        if node not in self.__edge_cost:
            return MAX_DEFAULT_DELAY
        else:
            return self.__edge_cost[node]

    def get_conn_in(self) -> List["Node"]:
        return self.__conn_ins

    def __iter__(self) -> Iterator["Node"]:
        return iter(self.__neighbors)

    def __len__(self):
        return len(self.__neighbors)

    @abstractmethod
    def __repr__(self):
        pass

    @abstractmethod
    def node_str(self):
        pass

    def clear(self):
        self.__neighbors.clear()
        self.__edge_cost.clear()
        self.__conn_ins.clear()

    def __contains__(self, item):
        return item in self.__neighbors

    def __hash__(self):
        return hash(self.width) ^ hash(self.x) ^ hash(self.y)
Example #2
0
def test_clear():
    set1 = OrderedSet('abracadabra')
    set1.clear()

    assert len(set1) == 0
    assert set1 == OrderedSet()
Example #3
0
def test_clear():
    set1 = OrderedSet('abracadabra')
    set1.clear()

    assert len(set1) == 0
    assert set1 == OrderedSet()
Example #4
0
class CCompiler(object):
    """Clara compiler. Compiles the application logical description,

    i.e. simple/conditional routing schema in a sets of instructions for a
    specified service. Below is an example of the application code, written in
    the specific Clara language:

    S1 + S2;
    if ( S1 == "abc" && S2 != "xyz") {
        S2 + S3;
    } elseif ( S1 == "fred" ) {
        S2 + S4;
    } else {
        S2 + S5,S6,S7;
    }
    S4,S5 + &S8;
    """

    _SYNTAX_ERROR = "Syntax error in the Clara routing program. Malformed " +\
                    "routing statement"

    def __init__(self, service_name):
        self._instructions = OrderedSet()
        self._service_name = service_name

    @staticmethod
    def _no_blanks(string):
        no_blank_str = string.strip()
        return ''.join(no_blank_str.split())

    def compile(self, composition):
        self._instructions.clear()
        ppi = list(self._pre_process(CCompiler._no_blanks(composition)))

        i = 0
        while i < len(ppi):
            scs1 = ppi[i]

            if (scs1.startswith("if(") or scs1.startswith("}if(")
                    or scs1.startswith("}else")
                    or scs1.startswith("}elseif(")):

                instruction = self._parse_condition(scs1)

                j = i + 1
                while j < len(ppi):
                    scs2 = ppi[j]
                    if (not scs2.startswith("}") and not scs2.startswith("if(")
                            and not scs2.startswith("}if(")
                            and not scs2.startswith("}elseif(")
                            and not scs2.startswith("}else")):
                        if instruction:
                            self._parse_conditional_statement(
                                scs2, instruction)
                    else:
                        i = j - 1
                        break
                    j += 1

                if instruction:
                    self._instructions.add(instruction)

            else:
                self._parse_statement(scs1)
            i += 1

        if not bool(self._instructions):
            raise ClaraException("Composition is irrelevant for a service.")

    @staticmethod
    def _pre_process(code_string):
        if code_string.find(";") == -1 and not code_string.endswith(";"):
            raise ClaraException("Syntax error in the Clara routing program." +
                                 "Missing end of statement operator = \";\"")
        instruction_set = []
        for text in code_string.split(";"):
            if text != "" and text != "}":
                instruction_set.append(text)

        return instruction_set

    def _parse_statement(self, statement_string):
        ti = Instruction(self._service_name)
        statement_string = remove_first(statement_string, "}")

        pattern = re.compile(Regex.ROUTING_STATEMENT)
        match = pattern.match(statement_string)

        if match:
            if not (self._service_name in statement_string):
                return False
            ts = Statement(statement_string, self._service_name)
            ti.unconditional_statements.add(ts)
            self._instructions.add(ti)

            return True
        else:
            raise ClaraException(self._SYNTAX_ERROR)

    def _parse_conditional_statement(self, statement_string, instruction):
        pattern = re.compile(Regex.ROUTING_STATEMENT)
        match = pattern.match(statement_string)
        if match:
            if not (self._service_name in statement_string):
                return False

            ts = Statement(statement_string, self._service_name)

            if instruction.if_condition:
                instruction.if_statements.add(ts)
            elif instruction.elseif_statements:
                instruction.elseif_statements.add(ts)
            else:
                instruction.else_statements.add(ts)

            return True
        else:
            raise ClaraException(self._SYNTAX_ERROR)

    def _parse_condition(self, condition):
        pattern = re.compile(Regex.CONDITION)
        match = pattern.match(condition)

        if match:
            try:
                index = condition.find("{") + 1
                statement_string = condition[index:]
                if not (self._service_name in statement_string):
                    return None
                ts = Statement(statement_string, self._service_name)
                ti = Instruction(self._service_name)

                if condition.startswith("if(") or condition.startswith("}if("):
                    par1 = condition.find("(") + 1
                    par2 = condition.rfind(")")

                    tc = Condition(condition[par1:par2], self._service_name)
                    ti.if_condition = tc
                    ti.if_statements.add(ts)

                elif condition.startswith("}elseif("):
                    par1 = condition.find("(") + 1
                    par2 = condition.rfind(")")

                    tc = Condition(condition[par1:par2], self._service_name)
                    ti.elseif_condition = tc
                    ti.elseif_statements.add(ts)

                elif condition.startswith("}else"):
                    ti.else_statements.add(ts)

                return ti

            except Exception as e:
                print e
                raise ClaraException(self._SYNTAX_ERROR)

        else:
            raise ClaraException(self._SYNTAX_ERROR)

    def get_unconditional_links(self):
        uncond = set()
        for instruction in self._instructions:
            for statement in instruction.unconditional_statements:
                uncond.update(statement.get_output_links())
        return uncond

    def get_links(self, owner_ss, input_ss):
        outputs = set()
        in_condition = False
        condition_chosen = False

        for instruction in self._instructions:
            if bool(instruction.unconditional_statements):
                in_condition = False

                for statement in instruction.unconditional_statements:
                    output = statement.get_output_links()
                    outputs.update(output)
                continue

            if instruction.if_condition:
                in_condition = True
                condition_chosen = False

                if instruction.if_condition.is_true(owner_ss, input_ss):
                    condition_chosen = True
                    for statement in instruction.if_statements:
                        output = statement.get_output_links()
                        outputs.update(output)
                continue

            if in_condition and not condition_chosen:
                if instruction.elseif_condition:
                    if instruction.elseif_condition.is_true(
                            owner_ss, input_ss):
                        condition_chosen = True
                        for statement in instruction.elseif_statements:
                            output = statement.get_output_links()
                            outputs.update(output)
                    continue

                if instruction.else_statements:
                    condition_chosen = True

                    for statement in instruction.else_statements:
                        output = statement.get_output_links()
                        outputs.update(output)
        return outputs
class CategorizedListbox(Frame):  #AKA CLB
    def __init__(self, parent, listview):
        Frame.__init__(self, parent)
        self.root = parent
        #creates the list that will house all controlled ModlistListboxes
        self.modlists = []
        self.selected_modlists = OrderedSet([])
        self.listview = listview
        self.current_index = 0
        self.grid_columnconfigure(0, weight=1)
        #checks for whether a pre-made CLB was input
        if len(self.modlists) <= 0:
            #Creates the default category 'Mods' when a new CLB is created
            self.insert(0, 'Mods')
        else:
            #Populates CLB with modlists given
            self.modlists = modlists

    def split(self, name, modlist_index, first, Last=None):
        '''Creates a new mod listbox with the mods of its original mod
        listbox based on the given indices, or an empty mod listbox'''
        modlist = self.modlists[modlist_index]
        if Last is None:
            pass

    def load(self, named_modlists):
        for i in range(len(self.modlists)):
            self.delete(0, force_delete=True)
        for named_modlist in named_modlists:
            self.insert(END, named_modlist[0], named_modlist[1])

    def get_mod_count(self, colors):
        n = 0
        for modlist in self.modlists:
            for mod in modlist.modlabel_list:
                if mod.color not in colors:
                    n += 1
##            n += len(modlist.modlabel_list)
        return 'Total Modcount: ' + str(n)

    def insert(self, index, name, modlist_info=None, is_collapsed=False):
        '''Create and insert a new modlist at the given modlist index'''
        modlist = ModlistListbox(self, self.listview, name)
        #if modlist info given, populate modlistbox with mods
        if modlist_info is not None and modlist_info != []:
            for i in range(len(modlist_info)):
                modlist.insert(i, modlist_info[i])
        #collapse mods if necessary
        if is_collapsed:
            modlist.force_collapse()
        #check last index values and set index accordingly
        if index == END or index >= len(self.modlists):
            index = len(self.modlists)
        #insert modlist into modlists list
        self.modlists.insert(index, modlist)
        #move modlists down if after inserted modlist
        if len(self.modlists) > index:
            for x in range(index, len(self.modlists)):
                self.modlists[x].grid(row=x, column=0, sticky='nsew')
        else:
            modlist.grid(row=len(self.mod_list), column=0, sticky='nsew')
        #Set modlist name label size
        self.update()

    def insert_input(self, index):
        if index == END:
            index = len(self.modlists)
        name = askstring('New Category at Index ' + str(index + 1),
                         'Name of new category:')
        if name is not None and name != '':
            self.insert(index, name)

    def merge_up(self, index):
        '''Merge the modlist at the given index with the modlist above it'''
        msgBox = messagebox.askquestion('Merge Categories Confirmation',
                                        'Merge "' + self.modlists[index].name +
                                        '" into "' +
                                        self.modlists[index - 1].name + '"?',
                                        icon='warning')
        if msgBox == 'yes':
            #populate list of mod info lists to add to and from, then get name
            l1 = self.modlists[index - 1].get_all_info()
            l2 = self.modlists[index].get_all_info()
            l_name = self.modlists[index - 1].get_name()
            #insert new merged modlist
            self.insert(index - 1, l_name, l1 + l2)
            #delete both previous modlists
            for x in range(2):
                self.delete(index)

    def merge_down(self, index):
        '''Merge the modlist at the given index with the modlist below it'''
        msgBox = messagebox.askquestion('Merge Categories Confirmation',
                                        'Merge "' + self.modlists[index].name +
                                        '" into "' +
                                        self.modlists[index + 1].name + '"?',
                                        icon='warning')
        if msgBox == 'yes':
            #populate list of mod info lists to add to and from, then get name
            l1 = self.modlists[index + 1].get_all_info()
            l2 = self.modlists[index].get_all_info()
            l_name = self.modlists[index + 1].get_name()
            #insert new merged modlist
            self.insert(index, l_name, l1 + l2)
            #delete both previous modlists
            for x in range(2):
                self.delete(index + 1)

    def delete_selected(self):
        '''delete all selected modlists'''
        if len(self.selected_modlists) == len(self.modlists):
            messagebox.showinfo('Selection Size Too Large',
                                'You cannot delete all the categories in the '
                                'list. There must always be at least one.',
                                icon='warning')
        elif len(self.selected_modlists) > 0:
            msgBox = messagebox.askquestion('Removing Selected Categories',
                                            'Remove all selected categories '
                                            'and their contents?',
                                            icon='warning')
            if msgBox == 'yes':
                for x in range(len(self.selected_modlists)):
                    self.delete(self.modlists.index(self.selected_modlists[0]))

    def delete_confirm(self, index):
        '''Add a confirmation to delete commands'''
        msgBox = messagebox.askquestion('Removing Category',
                                        'Remove the "' +
                                        self.modlists[index].name +
                                        '" Category and its contents?',
                                        icon='warning')
        if msgBox == 'yes':
            self.delete(index)

    def delete(self, index, force_delete=False):
        '''Delete a modlist at the given index'''
        if not force_delete and len(self.modlists) == 1:
            messagebox.showinfo(
                'Prohibited Action',
                'You must always have at least one category in the list.')
        else:
            if index == END:
                index = len(self.mod_list)
            if index < len(self.modlists) - 1:
                for x in range(index, len(self.modlists) - 1):
                    self.modlists[x + 1].grid(row=x, column=0, sticky='nsew')
            if self.modlists[index] in self.selected_modlists:
                self.selected_modlists.remove(self.modlists[index])
            self.modlists[index].grid_forget()
            self.modlists[index].destroy()
            del self.modlists[index]

    def delete_mod(self, modlist_index, mod_index):
        '''Delete a mod at the given indices'''
        mod = self.modlists[modlist_index].modlabel_list[mod_index]
        msgBox = messagebox.askquestion('Removing Mod',
                                        'Remove "{}"?'.format(
                                            mod.get_info()[1]),
                                        icon='warning')
        if msgBox == 'yes':
            self.modlists[modlist_index].delete(mod_index)

    def delete_selected_mod(self, event=None):
        selection_exists = False
        for modlist in self.modlists:
            if len(modlist.selected_modlabel_list) > 0:
                selection_exists = True
        if selection_exists:
            msgBox = messagebox.askquestion(
                'Removing Selected',
                'Remove selected mods from the list?',
                icon='warning')
            if msgBox == 'yes':
                for modlist in self.modlists:
                    modlist.delete_selected()

    def delete_all_mods(self):
        msgBox = messagebox.askquestion('Removing All',
                                        'Remove all mods from the list?',
                                        icon='warning')
        if msgBox == 'yes':
            for modlist in self.modlists:
                modlist.delete_all()

    def delete_all_cat(self, modlist_index):
        '''Delete all mods in a category at the given index'''
        modlist = self.modlists[modlist_index]
        msgBox = messagebox.askquestion('Removing All',
                                        'Remove all mods from the "' +
                                        modlist.name + '" Category?',
                                        icon='warning')
        if msgBox == 'yes':
            modlist.delete_all()

    def collapse_all(self):
        '''Collapses all mod listboxes'''
        for mod in self.modlists:
            if not mod.is_collapsed:
                mod.force_collapse()

    def expand_all(self):
        for mod in self.modlists:
            if mod.is_collapsed:
                mod.force_expand()

    def get_info(self):
        '''Gets a list of lists of mods throughout ALL modlists'''
        list = []
        for modlist in self.modlists:
            list.append(modlist.get_all_info())
        return list

    def get_all_info(self):
        '''Gets a list of lists of all modlists'''
        return self.modlists

    def rename(self, index):
        '''Rename a category at the given index by remaking the category'''
        name = askstring('Rename Category at Index ' + str(index + 1),
                         'New name of category:')
        if name is not None and name != '':
            data = self.modlists[index].get_all_info()
            is_collapsed = self.modlists[index].is_collapsed
            self.delete(index, force_delete=True)
            self.insert(index, name, data)
            self.modlists[index].forceSelectTop()
            if is_collapsed:
                self.modlists[index].force_collapse()

    #====passed or modified modlist functions====

    def onShiftClickEvent(self, event):
        if len(self.selected_modlists) > 0:
            #set original index to start multi-selection from
            origin = self.modlists.index(self.selected_modlists[-1])
            for x in range(len(self.modlists)):
                #checks every modlist for a valid multi-selection activation
                if self.modlists[x].is_top_entered:
                    #checks whether the index of the target modlists is above
                    #or below origin, then multi-selects accordingly
                    if (x - origin) > 0:
                        for y in range(origin, x + 1):
                            self.selected_modlists.append(self.modlists[y])
                            self.modlists[y].forceSelectTop()
                    elif (x - origin) < 0:
                        for y in range(x, origin):
                            self.selected_modlists.append(self.modlists[y])
                            self.modlists[y].forceSelectTop()
        else:
            for modlist in self.modlists:
                modlist.onShiftClickEvent(event)

    def dragSelection(self, event):
        '''Moves selected mods depending on mouse movement, and moves mods
        into and out of categories they are moved into and out of'''
        for modlist in self.modlists:
            modlist.dragSelection(event)

    def moveInto(self, direction, modlist):
        '''Depending on the direction, move the selected mods from the modlist
        into the modlist below or above it'''
        modlist_index = self.modlists.index(modlist)
        if direction == -1 and modlist_index != 0:
            #Move up
            for mod in sorted(modlist.selected_modlabel_list,
                              key=lambda x: x.get_index()):
                self.modlists[modlist_index - 1].insert(END, mod.get_info())
                #messy code to make the mod in the new category selected
                new_upper_mod = self.modlists[modlist_index -
                                              1].modlabel_list[-1]
                new_upper_mod.force_select()
                self.modlists[modlist_index -
                              1].selected_modlabel_list.append(new_upper_mod)
            selected_list_len = len(modlist.selected_modlabel_list)
            for i in range(selected_list_len):
                modlist.delete(0)
            modlist.selected_modlabel_list.clear()
            self.modlists[modlist_index - 1].force_expand()
        elif direction == 1 and modlist_index != len(self.modlists) - 1:
            #Move down
            for mod in sorted(modlist.selected_modlabel_list,
                              key=lambda x: x.get_index(),
                              reverse=True):
                self.modlists[modlist_index + 1].insert(0, mod.get_info())
                #messy code to make the mod in the new category selected
                new_lower_mod = self.modlists[modlist_index +
                                              1].modlabel_list[0]
                new_lower_mod.force_select()
                self.modlists[modlist_index +
                              1].selected_modlabel_list.append(new_lower_mod)
            selected_list_len = len(modlist.selected_modlabel_list)
            for i in range(selected_list_len):
                modlist.delete(END)
            modlist.selected_modlabel_list.clear()
            self.modlists[modlist_index + 1].force_expand()

    def moveSelectionUp(self, event=None):
        focused_widget = self.master.master.focus_get()
        if event is not None and type(focused_widget) in [
                Entry, Text
        ] and focused_widget.cget('state') == 'normal':
            return
        else:
            top_selected = False
            if len(self.selected_modlists) > 0:
                for modlist in self.modlists:
                    if modlist.is_top_selected:
                        top_selected = True
            if top_selected:
                sorted_selected_modlists = sorted(
                    self.selected_modlists,
                    key=lambda x: self.modlists.index(x))
                if sorted_selected_modlists[-1] == self.modlists[0]:
                    return
                for modlist in sorted_selected_modlists:
                    modlist_index = self.modlists.index(modlist)
                    list_to_move = self.modlists[modlist_index -
                                                 1].get_all_info()
                    list_to_move_name = self.modlists[modlist_index -
                                                      1].get_name()
                    list_to_move_is_collapsed = self.modlists[modlist_index -
                                                              1].is_collapsed
                    self.delete(modlist_index - 1)
                    self.insert(modlist_index, list_to_move_name, list_to_move,
                                list_to_move_is_collapsed)
                    #Collapse the category moved if it was collapsed
                    if list_to_move_is_collapsed:
                        self.modlists[modlist_index].force_collapse()
            else:
                for modlist in self.modlists:
                    n = 0
                    n = modlist.moveSelectionUp()
                    if n == -1:
                        self.moveInto(n, modlist)

    def moveSelectionDown(self, event=None):
        focused_widget = self.master.master.focus_get()
        if event is not None and type(focused_widget) in [
                Entry, Text
        ] and focused_widget.cget('state') == 'normal':
            return
        else:
            top_selected = False
            if len(self.selected_modlists) > 0:
                for modlist in self.modlists:
                    if modlist.is_top_selected:
                        top_selected = True
            if top_selected:
                sorted_selected_modlists = sorted(
                    self.selected_modlists,
                    key=lambda x: self.modlists.index(x))
                if sorted_selected_modlists[-1] == self.modlists[-1]:
                    return
                for modlist in sorted_selected_modlists:
                    modlist_index = self.modlists.index(modlist)
                    list_to_move = self.modlists[modlist_index +
                                                 1].get_all_info()
                    list_to_move_name = self.modlists[modlist_index +
                                                      1].get_name()
                    list_to_move_is_collapsed = self.modlists[modlist_index +
                                                              1].is_collapsed
                    self.delete(self.modlists.index(modlist) + 1)
                    self.insert(modlist_index, list_to_move_name, list_to_move,
                                list_to_move_is_collapsed)
                    #Collapse the category moved if it was collapsed
                    if list_to_move_is_collapsed:
                        self.modlists[modlist_index].force_collapse()
            else:
                for modlist in self.modlists:
                    n = 0
                    n = modlist.moveSelectionDown()
                    if n == 1:
                        self.moveInto(n, modlist)
                        return

    def onClickEvent(self, event):
        '''When the player clicks, control whether categories should be selected'''
        deselect_others = True
        #if clicked mod is already part of selection, prevents the deselection of other mods
        for x in range(len(self.modlists)):
            if self.modlists[x].is_top_entered and self.modlists[
                    x].is_top_selected:
                deselect_others = False
        if deselect_others:
            for x in range(len(self.modlists)):
                #Controls the selection of category names
                modlist = self.modlists[x]
                modlist.selectTop()
                if modlist.is_top_selected and modlist not in self.selected_modlists:
                    self.current_index = x
                    self.selected_modlists.append(modlist)
                elif not modlist.is_top_selected and modlist in self.selected_modlists:
                    self.selected_modlists.remove(modlist)
        for modlist in self.modlists:
            modlist.onClickEvent(event)

    def selectAll(self):
        for modlist in self.modlists:
            modlist.forceDeselectTop()
            modlist.selectAll()
        self.selected_modlists.clear()

    def insert_mod(self, modlist_index, mod_index):
        self.modlists[modlist_index].insertInput(mod_index)

    def insert_custom_mod(self, modlist_index, mod_index):
        self.modlists[modlist_index].insertCustomInput(mod_index)

    def batch_insert_mod(self, modlist_index, mod_index):
        l = []
        LinkGrabber(self, l, nexus=True)
        if len(l) == 1 and l[0] == False:
            messagebox.showinfo(
                'No Valid Data Found', 'Either none of the '
                'links provided were valid Nexus mod links, '
                'or the Nexus web server is currently unava'
                'ilable.')
        else:
            for info in reversed(l):
                self.modlists[modlist_index].insert(mod_index, info)

    def move_mod_to(self, modlist_index, target_modlist):
        modlist = self.modlists[modlist_index]
        for mod in sorted(modlist.selected_modlabel_list,
                          key=lambda x: x.get_index()):
            target_modlist.insert(END, mod.get_info())
        modlist.delete_selected()

    def rightClickMenu(self, event, rc_menu):
        ##        #Select proper categories
        ##        for i in self.modlists:
        ##            i.selectTop()
        self.onClickEvent(event)
        #Initialize submenus
        colors_menu = Menu(self.master.master, tearoff=0)
        remove_menu = Menu(self.master.master, tearoff=0)
        merge_menu = Menu(self.master.master, tearoff=0)
        select_menu = Menu(self.master.master, tearoff=0)
        links_menu = Menu(self.master.master, tearoff=0)
        move_menu = Menu(self.master.master, tearoff=0)
        #Get clicked indices and modlist
        modlist_index = self.dynamic_nearest()
        mod_index = self._get_clicked_mod_index(modlist_index)
        modlist = self.modlists[modlist_index]
        #General modlist commands
        rc_menu.add_command(
            label='Insert Nexus Mod Here...',
            command=lambda: self.insert_mod(modlist_index, mod_index))
        rc_menu.add_command(
            label='Insert Multiple Nexus Mods Here...',
            command=lambda: self.batch_insert_mod(modlist_index, mod_index))
        rc_menu.add_command(
            label='Insert Non-Nexus Mod Here...',
            command=lambda: self.insert_custom_mod(modlist_index, mod_index))
        y = self._get_clicked_cat_index(modlist_index)
        rc_menu.add_command(label='Insert Category Here...',
                            command=lambda: self.insert_input(y))
        rc_menu.add_command(label='Insert Category At End...',
                            command=lambda: self.insert_input(END))
        #Move options
        rc_menu.add_separator()
        rc_menu.add_cascade(label='Move Selected Mods To...', menu=move_menu)
        if len(modlist.modlabel_list) > 0 and \
        len(modlist.selected_modlabel_list) > 0:
            for ml in self.modlists:
                move_menu.add_command(label=ml.name,
                                      command=lambda ml=ml: self.move_mod_to( \
                                          modlist_index,ml))
                if ml == modlist:
                    move_menu.entryconfig(ml.name, state='disabled')
        #Color options
        if len(modlist.modlabel_list) > 0:
            rc_menu.add_separator()
            rc_menu.add_cascade(label="Change Selected Mods' Color To...",
                                menu=colors_menu)
            colors_menu.add_command(label='Default',
                                    command=lambda: \
                                    self.update_selected_colors('#383838'))
            colors_menu.add_command(label='Red',
                                    command=lambda: \
                                    self.update_selected_colors('red'))
            colors_menu.add_command(label='Blue',
                                    command=lambda: \
                                    self.update_selected_colors('blue'))
            colors_menu.add_command(label='Green',
                                    command=lambda: \
                                    self.update_selected_colors('green'))
            colors_menu.add_command(label='Yellow',
                                    command=lambda: \
                                    self.update_selected_colors('yellow'))
            rc_menu.add_separator()
            #incompatibilities commands
            rc_menu.add_command(label='Manage Incompatibilities...',
                                command=lambda: \
                                self.manage_incomp(modlist_index, mod_index))
            if len(self.modlists[modlist_index].modlabel_list[mod_index].
                   conflicts) > 0:
                rc_menu.add_command(label='View Conflicts',
                                    command=lambda: self.view_conflicts( \
                                        modlist_index, mod_index))
        rc_menu.add_separator()
        rc_menu.add_command(label='Rename Category',
                            command=lambda: self.rename(y))
        #Link options
        rc_menu.add_separator()
        rc_menu.add_command(
            label='Copy Mod Link',
            command=lambda: self.copyURL(modlist_index, mod_index))

        rc_menu.add_cascade(label='Open Links...', menu=links_menu)
        links_menu.add_command(label='Open Selected Mod Links',
                               command=self.open_selected)
        links_menu.add_command(label='Open All Mod Links in Category Here',
                               command=lambda x=modlist_index: self.openAll(x))
        #Selection options
        rc_menu.add_separator()
        rc_menu.add_cascade(label='Select...', menu=select_menu)
        select_menu.add_command(
            label='Select Here',
            command=lambda: modlist.rightClickSelect(mod_index))
        select_menu.add_command(label='Select All Mods in Category Here',
                                command=modlist.selectAll)
        select_menu.add_command(label='Select All Mods',
                                command=self.selectAll)
        #Merge options
        rc_menu.add_separator()
        rc_menu.add_cascade(label='Merge Category...', menu=merge_menu)
        merge_menu.add_command(label='Merge Category Here Into Upper',
                               command=lambda: self.merge_up(modlist_index))
        merge_menu.add_command(label='Merge Category Here Into Lower',
                               command=lambda: self.merge_down(modlist_index))
        if modlist_index == 0:
            merge_menu.entryconfig('Merge Category Here Into Upper',
                                   state='disabled')
        if modlist_index == len(self.modlists) - 1:
            merge_menu.entryconfig('Merge Category Here Into Lower',
                                   state='disabled')
        #Removal options
        rc_menu.add_separator()
        rc_menu.add_cascade(label='Remove...', menu=remove_menu)
        remove_menu.add_command(
            label='Remove Mod Here',
            command=lambda: self.delete_mod(modlist_index, mod_index))
        remove_menu.add_command(label='Remove Selected Mods',
                                command=self.delete_selected_mod)
        remove_menu.add_command(
            label='Remove All In Category',
            command=lambda: self.delete_all_cat(modlist_index))
        remove_menu.add_command(
            label='Remove Category Here',
            command=lambda: self.delete_confirm(modlist_index))
        remove_menu.add_command(label='Remove Selected Categories',
                                command=lambda: self.delete_selected())
        remove_menu.add_command(label='Remove All Mods',
                                command=self.delete_all_mods)
        #Disables the appropriate menu options
        if len(modlist.modlabel_list) == 0:
            remove_menu.entryconfig('Remove Mod Here', state='disabled')
            remove_menu.entryconfig('Remove All In Category', state='disabled')
            select_menu.entryconfig('Select Here', state='disabled')
            select_menu.entryconfig('Select All Mods in Category Here',
                                    state='disabled')
            links_menu.entryconfig('Open All Mod Links in Category Here',
                                   state='disabled')
        if len(self.selected_modlists) == 0:
            remove_menu.entryconfig('Remove Selected Categories',
                                    state='disabled')
        #Selects and deselects appropriate mods and categories
        i = 0
        for modlist in self.modlists:
            i += len(modlist.selected_modlabel_list)
            ##            modlist.onClickEvent(event)
            modlist.rightClickMenu(event, rc_menu)
        if i == 0:
            links_menu.entryconfig('Open Selected Mod Links', state='disabled')

    def view_conflicts(self, modlist_index, mod_index):
        conflicts = self.modlists[modlist_index].modlabel_list[
            mod_index].conflicts
        ConflictListbox(self, conflicts)

    def copyURL(self, modlist_index, mod_index):
        self.master.master.clipboard_clear()
        self.master.master.clipboard_append(
            self.modlists[modlist_index].modlabel_list[mod_index].get_info()
            [0])

    def openAll(self, modlist_index):
        msgBox = messagebox.askquestion('Opening All Mod Links',
                                        'Open all mod links in the "' +
                                        self.modlists[modlist_index].name +
                                        '" category in your default browser?',
                                        icon='warning')
        if msgBox == 'yes':
            self.modlists[modlist_index].open_all_links()

    def open_selected(self):
        for modlist in self.modlists:
            modlist.open_selected_links()

    def update_color(self, modlist_index, mod_index, color, state='normal'):
        '''Update a single mod's label color'''
        self.modlists[modlist_index].modlabel_list[ \
            mod_index].update_color(color,state)

    def update_selected_colors(self, color, state='normal'):
        for modlist in self.modlists:
            for mod in modlist.selected_modlabel_list:
                mod.update_color(color, state)

    def manage_incomp(self, modlist_index, mod_index):
        l = self.modlists[modlist_index].modlabel_list[
            mod_index].incompatibilities
        IncompatibilityManager(self, l)

    def _get_clicked_mod_index(self, modlist_index):
        '''return updated index if mouse is below the last mod in a given list'''
        modlist = self.modlists[modlist_index]
        mod_index = modlist.nearest()
        if modlist.listview:
            height = modlist.listview_height
        else:
            height = modlist.defaultview_height
        mouse_y = modlist.mlb_frame.winfo_pointery(
        ) - modlist.mlb_frame.winfo_rooty()
        if len(modlist.modlabel_list) > 1 and (height * mod_index +
                                               height) < mouse_y:
            return mod_index + 1
        else:
            return mod_index

    def _get_clicked_cat_index(self, modlist_index):
        '''return updated index if mouse is below the last category'''
        modlist = self.modlists[modlist_index]
        height = modlist.winfo_height()
        mouse_y = self.winfo_pointery() - self.winfo_rooty()
        if (modlist.winfo_y() + modlist.winfo_height()) < mouse_y:
            return modlist_index + 1
        else:
            return modlist_index

    def onDoubleClickEvent(self, event):
        for modlist in self.modlists:
            modlist.onDoubleClickEvent(event)

    def toggle_view(self):
        for modlist in self.modlists:
            modlist.toggle_view()

    def dynamic_nearest(self):
        '''get index of mod listbox nearest to the mouse y position.
        designed to work with widgets of variable sizes'''
        index = 0
        current_nearest_index = 0
        #get the absolute position of the mouse in relation to the ModlistListbox position
        mouse_y = self.winfo_pointery() - self.winfo_rooty()
        if len(self.modlists) > 1:
            #initialize y_maps, a list of 2-lengthed lists that store the
            #start and end y values of each modlist
            y_maps = []
            for i in range(len(self.modlists)):
                #populate heights
                modlist = self.modlists[i]
                #Set y-extending values
                if i == 0:
                    base = 0
                else:
                    base = y_maps[i - 1][1]
                if modlist.listview:
                    mod_height = modlist.listview_height * len(
                        modlist.modlabel_list)
                else:
                    mod_height = modlist.defaultview_height * len(
                        modlist.modlabel_list)
                #set start and end values
                if modlist.is_collapsed:
                    y_maps.append([base, base + modlist.name_height])
                else:
                    y_maps.append(
                        [base, base + modlist.name_height + mod_height])
            for i in range(len(y_maps)):
                #find the index within the y mappings
                if y_maps[i][0] <= mouse_y < y_maps[i][1]:
                    index = i
        return index
Example #6
0
class ModlistListbox(Frame):
    def __init__(self, parent, listview=False, name='Mods'):
        Frame.__init__(self, parent, bg='#2d2d2d')
        self.mod_list = []
        self.modlabel_list = []
        self.selected_modlabel_list = OrderedSet([])
        self.listview = listview
        self.listview_height = 29
        self.defaultview_height = 68
        self.name_height = 35
        self.current_index = None
        #category selection variables
        self.is_top_entered = False
        self.is_top_selected = False
        #collapse variable
        self.is_collapsed = False
        #enter variable
        self.is_entered_all = False
        #create mod listbox frame
        self.mlb_frame = Frame(self)
        #naming initalization
        self.name = name
        self.name_label = TagLabel(self,
                                   bordercolor='#666666',
                                   size=12,
                                   font=('roboto', 16, 'bold'),
                                   text=self.name,
                                   bg='#444444',
                                   fg='#f0f0f0',
                                   borderwidth=4,
                                   relief='flat')
        #limit the minimum size of name label
        if self.name_label.label.winfo_reqwidth() < 205:
            self.name_label.label.configure(width=15)
        #enter and leave events
        self.name_label.bind('<Enter>', self.on_enter_top)
        self.name_label.bind('<Leave>', self.on_leave_top)
        self.bind('<Enter>', self.on_enter_all)
        self.bind('<Leave>', self.on_leave_all)
        #lay out the name frame and mod listbox frame
        self.name_label.pack(side='top', fill='y', anchor='sw', padx=10)
        self.mlb_frame.pack(side='bottom', fill='both', expand=True)
        self.mlb_frame.grid_columnconfigure(0, weight=1)
        self.update_idletasks()

    def update_name(self, name):
        self.name = name
        ##        self.name_label.update_text(name)
        #resizing a pre-existing TagLabel isn't working when changing text
        #so just make a new one 4Head
        new_name = TagLabel(self,
                            bordercolor='#666666',
                            size=12,
                            font=('roboto', 16, 'bold'),
                            text=name,
                            bg='#444444',
                            fg='#f0f0f0',
                            borderwidth=4,
                            relief='flat')
        self.name_label.pack_forget()
        self.name_label.destroy()
        self.name_label = new_name
        self.name_label.pack(side='top', fill='y', anchor='sw', padx=10)
        if self.name_label.label.winfo_reqwidth() < 205:
            self.name_label.label.configure(width=15)

    def get_name(self):
        return self.name

    def toggle_collapse(self):
        '''Collapses or expands the mod listbox'''
        if len(self.modlabel_list) > 0:
            if self.is_collapsed:
                self.force_expand()
            else:
                self.force_collapse()
            self.update_idletasks()

    def force_collapse(self):
        if len(self.modlabel_list) > 0:
            for mod in self.modlabel_list:
                mod.grid_remove()
                self.mlb_frame.configure(height=1)
            self.is_collapsed = True
            self.name_label.configure(bg='white')

    def force_expand(self):
        if len(self.modlabel_list) > 0:
            for mod in self.modlabel_list:
                mod.grid()
            self.is_collapsed = False
            self.name_label.configure(bg='#666666')

    def insert(self, index, info):
        '''inserts a mod at the given index, and with the given info'''
        if index == END or index > len(self.mod_list):
            index = len(self.mod_list)
        mod_label = ModLabel(self.mlb_frame,
                             info=info,
                             index=index,
                             listview=self.listview)
        #Try to update the new indices. Fails if from an older mod
        try:
            mod_label.update_color(info[6])
            mod_label.incompatibilities = info[7]
        except IndexError:
            pass
        self.mod_list.insert(index, info)
        self.modlabel_list.insert(index, mod_label)
        if len(self.mod_list) > index:
            for x in range(index, len(self.mod_list)):
                self.modlabel_list[x].update_index(x)
                self.modlabel_list[x].grid(row=x, column=0, sticky='nsew')
        else:
            mod_label.grid(row=len(self.mod_list), column=0, sticky='nsew')
        if self.is_collapsed:
            self.force_expand()

    def delete(self, index):
        '''Delete a mod at the given index'''
        if index == END:
            index = len(self.mod_list) - 1
        if index < len(self.mod_list) - 1:
            for x in range(index, len(self.mod_list) - 1):
                self.modlabel_list[x + 1].grid(row=x, column=0, sticky='nsew')
                self.modlabel_list[x + 1].update_index(x)
        if self.modlabel_list[index] in self.selected_modlabel_list:
            self.selected_modlabel_list.remove(self.modlabel_list[index])
        self.modlabel_list[index].grid_forget()
        self.modlabel_list[index].destroy()
        del self.mod_list[index]
        del self.modlabel_list[index]
        if len(self.modlabel_list) == 0:
            self.mlb_frame.configure(height=1)
        if self.is_collapsed:
            self.force_expand()
            #Quick fix for a category not properly expanding if last mod deleted
            self.is_collapsed = False
            self.name_label.configure(bg='#666666')

    def delete_selected(self):
        for x in range(len(self.selected_modlabel_list)):
            self.delete(self.selected_modlabel_list[0].get_index())

    def delete_all_confirm(self):
        msgBox = messagebox.askquestion('Removing All',
                                        'Remove all mods from the ' +
                                        self.name + ' Category?',
                                        icon='warning')
        if msgBox == 'yes':
            self.delete_all()

    def delete_all(self):
        '''deletes all mods from the list'''
        for x in range(len(self.modlabel_list)):
            self.delete(0)
        #Quick fix for a category not properly expanding if last mod deleted
        self.is_collapsed = False
        self.name_label.configure(bg='#666666')

    def open_link(self, modlabel):
        webbrowser.open_new(modlabel.get_info()[0])

    def open_selected_links(self):
        for mod in sorted(self.selected_modlabel_list,
                          key=lambda x: x.get_index()):
            self.open_link(mod)

    def open_all_links(self):
        for mod in self.modlabel_list:
            self.open_link(mod)

    def get_size(self):
        return len(self.modlabel_list)

    def get_all_info(self):
        '''return a list of lists of all mod info'''
        l = []
        for mod in self.modlabel_list:
            l.append(mod.get_info())
        return l

    def get_info(self, index):
        '''return a list of the mod info at the given index'''
        return self.modlabel_list[index].get_info()

    def get_height(self):
        return len(self.modlabel_list)

    def toggle_view(self):
        for mod in self.modlabel_list:
            mod.toggle_view()
        self.listview = not self.listview

    def force_listview(self):
        for mod in self.modlabel_list:
            mod.display_listview()
        self.listview = True

    def force_defaultview(self):
        for mod in self.modlabel_list:
            mod.display_default()
        self.listview = False

    def on_enter_top(self, event):
        '''event to determine if the list's category title is focused'''
        self.is_top_entered = True

    def on_leave_top(self, event):
        self.is_top_entered = False

    def on_enter_all(self, event):
        self.is_entered_all = True

    def on_leave_all(self, event):
        self.is_entered_all = False

    #====Right Click Menu Functionality

    def rightClickMenu(self, event, rc_menu):
        for mod in self.modlabel_list:
            if mod.is_focused:
                #If the mod clicked is not selected, select it
                if mod not in self.selected_modlabel_list:
                    self.onClickEvent(event)
##                rc_menu.add_command(label='Select',
##                                    command=lambda mod=mod: self.rightClickSelect(mod))
##                rc_menu.add_command(label='Select All Mods in "'+self.name+'" Category',
##                                    command=self.selectAll)
##                rc_menu.add_separator()
##                rc_menu.add_separator()
##                rc_menu.add_command(label='Remove',
##                                    command=lambda mod=mod: self.delete(mod.get_index()))
##                if len(self.selected_modlabel_list) > 0:
##                    rc_menu.add_command(label='Remove Selected',
##                                        command=self.delete_selected)
##                if len(self.modlabel_list) > 0:
##                    rc_menu.add_command(label='Remove All Mods In "'+self.name+'" Category',
##                                        command=self.delete_all_confirm)

    def insertInput(self, index):
        url = askstring('New Mod at Index ' + str(index + 1),
                        'Input Nexus URL')
        info = None
        if (url is not None):
            info = ParseURL.parse_nexus_url(url)
        if info is not None:
            self.insert(index, info)

    def insertCustomInput(self, index):
        info = []
        CM = CustomModMessageBox(self, 'New Mod at Index ' + str(index + 1),
                                 info)
        if info != []:
            self.insert(index, info)

    #====Selection Functionality====

    def onClickEvent(self, event):
        deselect_others = True
        self._check_descs()
        if len(self.modlabel_list) > 0:
            #if clicked mod is already part of selection, prevents the deselection of other mods
            for x in range(len(self.modlabel_list)):
                if self.modlabel_list[
                        x].is_index_focused and self.modlabel_list[
                            x].is_selected:
                    deselect_others = False
                    self.current_index = x
            if deselect_others:
                #checks every modlabel to see if any are selected
                for x in range(len(self.modlabel_list)):
                    self.modlabel_list[x].select()
                    #code for multi-selection capabilities
                    if self.modlabel_list[x].is_selected and self.modlabel_list[
                            x] not in self.selected_modlabel_list:
                        #adds selected modlabels to the selected modlabels list
                        self.current_index = x
                        self.selected_modlabel_list.append(
                            self.modlabel_list[x])
                    elif not self.modlabel_list[
                            x].is_selected and self.modlabel_list[
                                x] in self.selected_modlabel_list:
                        #removes deselected modlabels from the selected modlabels list
                        self.selected_modlabel_list.remove(
                            self.modlabel_list[x])

    def _check_descs(self):
        '''checks whether mouse is over a mod description or not'''
        #Get widget type under mouse
        x, y = self.winfo_pointerxy()
        widget = self.winfo_containing(x, y)
        for mod in self.modlabel_list:
            if widget is not mod.description:
                mod.disable_desc_edit()
            else:
                mod.enable_desc_edit()

    def onDoubleClickEvent(self, event):
        if self.is_top_entered and len(self.modlabel_list) > 0:
            self.toggle_collapse()

    def onShiftClickEvent(self, event):
        #code for multi-selection
        if len(self.selected_modlabel_list) > 0:
            #set original index to start multi-selection from
            origin = self.selected_modlabel_list[-1].get_index()
            for x in range(len(self.modlabel_list)):
                # checks every modlabel for a valid multi-selection activation
                if self.modlabel_list[x].is_index_focused:
                    #checks whether the index of the target modlabel is above
                    #or below origin, then multi-selects accordingly
                    if (x - origin) > 0:
                        for y in range(origin, x + 1):
                            self.selected_modlabel_list.append(
                                self.modlabel_list[y])
                            self.modlabel_list[y].force_select()
                    elif (x - origin) < 0:
                        for y in range(x, origin):
                            self.selected_modlabel_list.append(
                                self.modlabel_list[y])
                            self.modlabel_list[y].force_select()

    def rightClickSelect(self, mod_index):
        mod = self.modlabel_list[mod_index]
        self.deselectAll()
        mod.force_select()
        self.selected_modlabel_list.append(mod)

    def selectAll(self):
        '''Selects all the mods'''
        if len(self.modlabel_list) > 0:
            for mod in self.modlabel_list:
                self.selected_modlabel_list.append(mod)
                mod.force_select()

    def deselectAll(self):
        if len(self.selected_modlabel_list) > 0:
            for mod in self.selected_modlabel_list:
                mod.force_deselect()
            self.selected_modlabel_list.clear()

    def selectTop(self):
        if self.is_top_entered:
            self.forceSelectTop()
        else:
            self.forceDeselectTop()

    def forceSelectTop(self):
        self.is_top_selected = True
        self.name_label.label.configure(bg='#f0f0f0', fg='#444444')

    def forceDeselectTop(self):
        self.is_top_selected = False
        self.name_label.label.configure(bg='#444444', fg='#f0f0f0')

    def is_top_selected(self):
        return self.is_top_selected

    #====Drag and Drop Functionality====

    def moveSelectionUp(self):
        '''Goes through the selected mods and moves them up by 1'''
        if len(self.selected_modlabel_list) > 0:
            for mod in self.selected_modlabel_list:
                #if mod at upper limit, don't move anything
                if mod.get_index() == 0:
                    return -1
            #sorts selected mods using the index as the key, then iterates
            for mod in sorted(self.selected_modlabel_list,
                              key=lambda x: x.get_index()):
                modtomove_data = self.modlabel_list[mod.get_index() -
                                                    1].get_info()
                modtomove_index = mod.get_index() - 1
                self.delete(modtomove_index)
                self.insert(modtomove_index + 1, modtomove_data)

    def moveSelectionDown(self):
        '''Goes through the selected mods and moves them down by 1'''
        if len(self.selected_modlabel_list) > 0:
            for mod in self.selected_modlabel_list:
                #if mod at lower limit, don't move anything
                if mod.get_index() == len(self.modlabel_list) - 1:
                    return 1
            #sorts selected mods using the index as the key, then iterates
            for mod in sorted(self.selected_modlabel_list,
                              key=lambda x: x.get_index(),
                              reverse=True):
                modtomove_data = self.modlabel_list[mod.get_index() +
                                                    1].get_info()
                modtomove_index = mod.get_index() + 1
                self.delete(modtomove_index)
                self.insert(modtomove_index - 1, modtomove_data)

    def dragSelection(self, event):
        '''Moves all selected ModLabels up or down depending on
        mouse movement while the mouse is held'''
        i = self.nearest()
        if self.current_index is not None:
            if i < self.current_index and i != -1:
                self.moveSelectionUp()
                self.current_index = i
            elif i > self.current_index and i != len(self.modlabel_list):
                self.moveSelectionDown()
                self.current_index = i
        return i

    def nearest(self):
        '''get index of ModLabel item nearest to the mouse y position
        using hardcoded ModLabel heights'''
        index = 0
        current_nearest_index = 0
        #get the correct height of the ModLabels
        if self.listview:
            height = self.listview_height
        else:
            height = self.defaultview_height
        #get the absolute position of the mouse in relation to the ModlistListbox position
        mouse_y = self.mlb_frame.winfo_pointery() - self.mlb_frame.winfo_rooty(
        )
        if len(self.modlabel_list) > 0:
            current_index = 0
            distance_from_current = abs((height / 2) - mouse_y)
            for i in range(len(self.modlabel_list)):
                distance_from_next = abs((i * height + (height / 2)) - mouse_y)
                if distance_from_current > distance_from_next:
                    distance_from_current = distance_from_next
                    current_index = i
            #if going beyond the list, return an index beyond the list to signify it moving into a different category
##            if(current_index =
            index = current_index
        return index

    #====Code that doesn't f****n' work====
    '''NOTES: This nearest() is based off of each widget's actual position on the board. It bugs out when a mod is
Example #7
0
class Tile:

    def __init__(self, x: int, y: int,
                 track_width: int,
                 switchbox: SwitchBox,
                 height: int = 1):
        self.x = x
        self.y = y
        self.track_width = track_width
        self.height = height

        # create a copy of switch box because the switchbox nodes have to be
        # created
        self.switchbox: SwitchBox = SwitchBox(x, y, switchbox.num_track,
                                              switchbox.width,
                                              switchbox.internal_wires)

        self.ports: Dict[str, PortNode] = {}

        self.inputs = OrderedSet()
        self.outputs = OrderedSet()

        # hold for the core
        self.core: InterconnectCore = None

    def __eq__(self, other):
        if not isinstance(other, Tile):
            return False
        return self.x == other.x and self.y == other.y and \
            self.height == other.height

    def __repr__(self):
        return f"TILE ({self.x}, {self.y}, {self.height}, " +\
               f"{self.switchbox.id})"

    def set_core(self, core: InterconnectCore):
        self.inputs.clear()
        self.outputs.clear()
        self.ports.clear()
        self.core = core

        # this is to clear to core
        if core is None:
            return

        inputs = core.inputs()[:]
        inputs.sort(key=lambda x: x[1])
        for width, port_name in inputs:
            if width == self.track_width:
                self.inputs.add(port_name)
                # create node
                self.ports[port_name] = PortNode(port_name, self.x,
                                                 self.y, width)
        outputs = core.outputs()[:]
        outputs.sort(key=lambda x: x[1])
        for width, port_name in outputs:
            if width == self.track_width:
                self.outputs.add(port_name)
                # create node
                self.ports[port_name] = PortNode(port_name, self.x,
                                                 self.y, width)

    def core_has_input(self, port: str):
        return port in self.inputs

    def core_has_output(self, port: str):
        return port in self.outputs

    def name(self):
        return str(self)

    def get_sb(self, side: SwitchBoxSide,
               track: int,
               io: SwitchBoxIO) -> Union[SwitchBoxNode, None]:
        return self.switchbox.get_sb(side, track, io)

    def set_core_connection(self, port_name: str,
                            connection_type: List[SBConnectionType]):
        # make sure that it's an input port
        is_input = self.core_has_input(port_name)
        is_output = self.core_has_output(port_name)

        if not is_input and not is_output:
            # the core doesn't have that port_name
            return
        elif not (is_input ^ is_output):
            raise ValueError("core design error. " + port_name + " cannot be "
                             " both input and output port")

        port_node = self.ports[port_name]
        # add to graph node first, we will handle magma in a different pass
        # based on the graph, since we need to compute the mux height
        for side, track, io in connection_type:
            sb = self.get_sb(side, track, io)
            if is_input:
                sb.add_edge(port_node)
            else:
                port_node.add_edge(sb)

    @staticmethod
    def create_tile(x: int, y: int, bit_width: int, num_tracks: int,
                    internal_wires: List[Tuple[int, SwitchBoxSide,
                                               int, SwitchBoxSide]],
                    height: int = 1) -> "Tile":
        switch = SwitchBox(x, y, num_tracks, bit_width, internal_wires)
        tile = Tile(x, y, bit_width, switch, height)
        return tile

    def clone(self):
        # clone the switchbox
        switchbox = self.switchbox.clone()
        tile = Tile(self.x, self.y, self.track_width, switchbox, self.height)
        # tile creates an empty copy of it, so we have to replace it
        tile.switchbox = switchbox
        # we don't clone the cores
        tile.set_core(self.core)
        return tile