コード例 #1
0
    def getPermutation(self, n: int, k: int) -> str:
        factorial = [1, 1]  # list where each element is factorial of index

        # figure out what number factorial is just above OR EQUAL TO k
        b = 1
        while factorial[b] < k:
            b += 1
            factorial.append(factorial[b - 1] * b)

        answer = ''.join([str(i) for i in range(1, n - b + 1)])
        remaining = OrderedSet(i for i in range(n - b + 1, n + 1))

        while b > 0:
            # figure out which of the b digits to move here
            next_digit_indexindex = k // factorial[b - 1]
            oneless = not k % factorial[b - 1]
            if oneless:
                next_digit_indexindex -= 1

            # do the swap, keeping the tail in increasing order
            digit = remaining[next_digit_indexindex]
            remaining.remove(digit)
            answer += str(digit)

            # update k because we have already calculated part of the permutation
            k -= next_digit_indexindex * (factorial[b - 1])
            # on to the next digit
            b -= 1
        return answer
コード例 #2
0
ファイル: cyclone.py プロジェクト: Kuree/canal
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)
コード例 #3
0
ファイル: routes.py プロジェクト: Evgeny0088/Route_finder
class Routes:
    def __init__(self):
        self.routes = {}
        self.route_number = 0
        self.route_list = OrderedSet()
        self.route = ''

    @property
    def _get_route_list(self):
        return self.route_list

    @property
    def _get_routes(self):
        return self.routes

    def _add(self, point):
        self.route_list.add(point)
        self.route = '-'.join(self.route_list)

    def _remove(self, point):
        self.route_list.remove(point)

    def _set_route(self):
        def _route_cost(_set, cost=0):
            _set = list(_set)
            for i in range(len(_set) - 1):
                if any(_set[i + 1] == k for k in nodes[_set[i]].keys()):
                    cost += nodes[_set[i]][_set[i + 1]]
            return cost

        if len(self.route) > 1:
            result = self.route
            if result not in self.routes.values():
                self.route_number += 1
                cost = _route_cost(self.route_list)
                self.routes[self.route_number] = result, cost
                self.route_list = self.route_list[:-1]
                self.route = ''

    @staticmethod
    def cheapest_route(routes):
        cost = tuple()
        if routes:
            values = routes.values()
            value_iter = iter(values)
            cost = next(value_iter)
            c = cost[1]
            for i in routes.keys():
                if c > routes[i][1]:
                    c = routes[i][1]
                    cost[0] = routes.get(i)[0]
            return f'cheapest route is: {cost}'
        return f'There is not routes...'

    def __str__(self):
        return str(self.routes)
コード例 #4
0
ファイル: test.py プロジェクト: LuminosoInsight/ordered-set
def test_remove():
    set1 = OrderedSet('abracadabra')

    set1.remove('a')
    set1.remove('b')

    assert set1 == OrderedSet('rcd')
    assert set1[0] == 'r'
    assert set1[1] == 'c'
    assert set1[2] == 'd'

    assert set1.index('r') == 0
    assert set1.index('c') == 1
    assert set1.index('d') == 2

    assert 'a' not in set1
    assert 'b' not in set1
    assert 'r' in set1

    # Make sure we can .discard() something that's already gone, plus
    # something that was never there
    set1.discard('a')
    set1.discard('a')
コード例 #5
0
ファイル: test.py プロジェクト: LuminosoInsight/ordered-set
def test_remove_error():
    # If we .remove() an element that's not there, we get a KeyError
    set1 = OrderedSet('abracadabra')
    with pytest.raises(KeyError):
        set1.remove('z')
コード例 #6
0
from ordered_set import OrderedSet

from ..utils import SimpleEnumMeta


class Color(metaclass=SimpleEnumMeta):
    BLACK = 'BLACK'
    BLUE = 'BLUE'
    RED = 'RED'
    YELLOW = 'YELLOW'
    ALL = 'ALL'


all_colors = OrderedSet(Color)
all_colors.remove(Color.ALL)


class ColorSet(set):
    '''Set that contains values of the Color Enum.Color.

    Convert string to the proper color on addition or update if necessary.
    '''

    def __init__(self, colors=None):
        super()
        if colors is None:
            colors = []

        self.update(colors)
コード例 #7
0
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
コード例 #8
0
class VcpStorageStorable(metaclass=ABCMeta):
    """ Methods that modify the instance. This list is used to generate wrapper methods automatically in the FallbackVcpStorageStorable classes """
    WRITE_METHODS = ('add_name', 'add_names', 'remove_name', 'remove_names',
                     'clear_names')

    def __init__(self, *args, **kwargs):
        instance_parent = None
        if not isinstance(self, HierarchicalMixin):
            instance_parent = kwargs.pop('instance_parent', None)

        super().__init__(*args, **kwargs)

        if not isinstance(self, HierarchicalMixin):
            self.instance_parent = instance_parent

        self._initialize()

    def _initialize(self):
        self._names = OrderedSet()

    @abstractmethod
    def vcp_storage_key(self) -> T_VcpStorageKey:
        pass

    @abstractmethod
    def vcp_storage_key_name(self) -> str:
        pass

    # Name(s)
    @property
    def has_name(self) -> bool:
        return len(self._names) > 0

    @property
    def name(self) -> T_VcpStorageName:
        if self.has_name:
            return str(self._names[0])
        return f"0x{self.vcp_storage_key():X}"

    @property
    def names(self) -> Iterable[T_VcpStorageName]:
        return OrderedSet(self._names)

    def add_name(self, new_name: T_VcpStorageName) -> None:
        if isinstance(new_name, int):
            raise ValueError(f"new_name={new_name} cannot be an integer")

        self._names.add(new_name)

        from .storage import VcpStorage
        if isinstance(self.instance_parent, VcpStorage):
            self.instance_parent[new_name] = self.vcp_storage_key()

    def add_names(self, *names: Iterable[T_VcpStorageName]) -> None:
        for name in names:
            self.add_name(name)

    def remove_name(self, name: T_VcpStorageName) -> None:
        if isinstance(name, int):
            raise ValueError(f"name={name} cannot be an integer")

        if name not in self._names:
            return

        self._names.remove(name)

        from .storage import VcpStorage
        if isinstance(self.instance_parent, VcpStorage):
            del self.instance_parent[name]

    def remove_names(self, *names: Iterable[T_VcpStorageName]) -> None:
        for name in names:
            self.remove_name(name)

    def clear_names(self) -> None:
        self.remove_names(*list(self._names))

    # Comparison, etc
    def __eq__(
            self, other: Union['VcpStorageStorable',
                               T_VcpStorageIdentifier]) -> bool:
        if isinstance(other, self.__class__):
            return other is self

        if isinstance(other, int):
            return other == self.vcp_storage_key()

        if isinstance(other, str):
            from .storage import VcpStorage
            key = VcpStorage.standardise_identifier(other)

            for nm in self.names:
                nm_key = VcpStorage.standardise_identifier(nm)
                if key == nm_key:
                    return True

            return False

        return False

    def __hash__(self) -> int:
        return self.vcp_storage_key()

    # Copying
    def copy_storable(self, other: 'VcpStorageStorable') -> None:
        assert self.vcp_storage_key() == other.vcp_storage_key()
        assert self.__class__ is other.__class__

        self.add_names(*other.names)

    # Conversion
    def asdict(self, include_key=False) -> Dict[str, Any]:
        d = {}

        if self.has_name:
            d['name'] = self.name

            if len(self._names) > 1:
                aliases = []
                for nm in self._names[1:]:
                    aliases.append(str(nm))
                d['aliases'] = aliases

        storage_key_nm = self.vcp_storage_key_name()
        for attr_nm in self.__dict__:
            if attr_nm[0] != '_' and attr_nm != storage_key_nm:
                attr = getattr(self, attr_nm)

                if attr is not None:
                    d[attr_nm] = attr

        if include_key:
            d[storage_key_nm] = self.vcp_storage_key()

        return d

    def _fromdict(self, data: Dict) -> None:
        self.clear_names()

        if 'name' in data:
            self.add_name(data['name'])

        if 'aliases' in data:
            self.add_names(*data['aliases'])

        storage_key_nm = self.vcp_storage_key_name()
        for attr_nm in self.__dict__:
            if attr_nm[0] != '_' and attr_nm != storage_key_nm:
                attr = data.get(attr_nm, None)
                if attr is not None:
                    setattr(self, attr_nm, attr)

    # Printing
    def __str__(self) -> str:
        return self.name
コード例 #9
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