예제 #1
0
    def getParents(self,
                   includeSelf=False,
                   includeApp=False,
                   includeParts=False):
        """
        Retrieves parent pages from element or page going all the way up to a top page that has App as it's 'parentPage' attribute.

        :param generalgui.element.Element or generalgui.page.Page or generalgui.app.App self: Element, Page or App
        :param includeSelf: Whether to include self or not (Element or Page) as index 0
        :param includeApp: Whether to include app or not
        :param includeParts: Whether to include parts or not
        :rtype: list[generalgui.element.Element or generalgui.page.Page]
        """
        pages = []

        if typeChecker(self, "App", error=False):
            if includeApp or includeSelf:
                return [self]
            else:
                return []

        parentPage = self.getParentPartOrPage(
        ) if includeParts else self.parentPage
        while True:
            if typeChecker(parentPage, "App", error=False):
                if includeSelf:
                    pages.insert(0, self)
                if includeApp:
                    pages.append(parentPage)
                return pages
            else:
                pages.append(parentPage)
            parentPage = parentPage.getParentPartOrPage(
            ) if includeParts else parentPage.parentPage
예제 #2
0
    def packPart(self, element):
        """
        Don't pack at all, only check type with whitelist

        :param element:
        """
        typeChecker(element, ("Label", "Entry", "Checkbutton"))
예제 #3
0
def _cellValue(func, self, args, kwargs, index=False, header=False):
    """Helper for indexValue and headerValue decorators"""
    cellValue = getParameter(func, args, kwargs, "cellValue")

    element = None
    if cellValue is None:
        if self.app.menuTargetElement is None:
            raise ValueError(
                "cellValue is None and app.menuTargetElement is None")

        element = self.app.menuTargetElement
        if not typeChecker(element, ("Button", "Label"),
                           error=False):  # Because element can be Frame
            return

    elif typeChecker(cellValue, "Event", error=False):
        event = cellValue
        element = event.widget.element

    if element is not None:
        grid = element.parentPage
        spreadsheet = grid.getFirstParentByClass("Spreadsheet")
        if grid == spreadsheet.mainGrid:
            gridPos = grid.getGridPos(element)
            if index:
                value = spreadsheet.dataFrame.index[gridPos.y - 1]
            elif header:
                value = spreadsheet.dataFrame.columns[gridPos.x]
            else:
                raise ValueError("index or header has to be True")
        else:
            value = element.getValue()
        args, kwargs = changeArgsAndKwargs(func, args, kwargs, cellValue=value)

    return func(self, *args, **kwargs)
예제 #4
0
def styleDecorator_helper(styleHandler, style):
    """Helper for decorator."""
    if isinstance(style, str):
        if style in styleHandler.allStyles:
            style = styleHandler.allStyles[style]
        else:
            return None
            # raise AttributeError(f"Style with name {style} doesn't exist")
    if isinstance(style, Style):
        if style.name not in styleHandler.allStyles:
            raise AttributeError(
                f"Style with name {style.name} has been deleted")

    typeChecker(style, ("Style", None))
    return style
예제 #5
0
    def pack(self):
        """
        Packs this Element's widget using the packParameters attribute.

        :param generalgui.element.Element or generalgui.Page self: Element or Page
        """
        if typeChecker(self, "Page", error=False):
            if self.topElement is None:
                raise AttributeError("Cannot pack Page without a topElement.")
            self.topElement.pack()

        else:

            if self.hasGridParameters():
                self._grid()

            else:
                try:
                    self.getParentPartPage().packPart(self)
                    # self.parentPart.parentPage.packPart(self)
                except TclError as e:
                    print(self, self.packParameters, self.parentPart)
                    raise e

            if self.parentPage.scrollable:
                self.app.widget.update()  # To get correct scroll region
                self.parentPage.canvas.callBind(
                    "<Configure>")  # Update canvas scroll region manually
예제 #6
0
    def getParentPartPage(self):
        """
        Get the page of this element's or page's parentPart

        :param generalgui.element.Element or generalgui.Page self: Element or Page
        """
        if typeChecker(self.parentPart, "App", error=False):
            return self.app
        else:
            return self.parentPart.parentPage
예제 #7
0
    def loadDataFrame(self, df=None):
        """
        Update cells to represent a dataFrame with any types of values.
        """
        self.dataFrameIsLoading = True

        if df is not None:
            if not typeChecker(df, pd.DataFrame, error=False):
                df = pd.DataFrame(df)

            self.dataFrame = df
        df = self.dataFrame
        # print(df.to_string())

        if self.columnKeys:
            size = Vec2(len(df.columns), 1)
            self.headerGrid.fillGrid(Frame, Vec2(1, 0), size, height=1)
            self.mainGrid.fillGrid(Frame, Vec2(1, 0), size, height=1)

            self.headerGrid.fillGrid(
                Label,
                Vec2(1, 1),
                size,
                values=df.columns,
                removeExcess=True,
                onClick=lambda e: self.sortColumn(cellValue=e),
                anchor="c")

        if self.rowKeys:
            size = Vec2(1, len(df.index))
            self.indexGrid.fillGrid(Frame, Vec2(0, 1), size, width=1)
            self.mainGrid.fillGrid(Frame, Vec2(0, 1), size, width=1)

            self.indexGrid.fillGrid(
                Label,
                Vec2(1, 1),
                size,
                values=df.index,
                removeExcess=True,
                onClick=lambda e: self.sortRow(cellValue=e))

        values = []
        for row in df.itertuples(index=False):
            values.extend(row)
        self.mainGrid.fillGrid(Label,
                               Vec2(1, 1),
                               Vec2(df.shape[1], df.shape[0]),
                               values=values,
                               removeExcess=True,
                               color=True,
                               **self.cellConfig)

        self.dataFrameIsLoading = False
        self.syncSizes()
예제 #8
0
    def getFirstParentByClass(self, className, includeSelf=False):
        """
        Iterate parent pages to return first part with matching className or None.

        :param generalgui.element.Element or generalgui.page.Page or generalgui.app.App self: Element, Page or App
        :param className: Name or type of part, used by typeChecker
        :param includeSelf:
        """
        for part in self.getParents(includeSelf=includeSelf):
            if typeChecker(part, className, error=False):
                return part
예제 #9
0
    def maximize(self):
        """
        Maximize this page or app.
        Pages' size are just set to 10000 because tkinter seems to handle it nicely.

        :param generalgui.page.Page or generalgui.app.App self: Page or App
        :return:
        """
        if typeChecker(self, "Page", error=False):
            self.setSize(10000)
        self.app.widget.state("zoomed")
예제 #10
0
        def _wrapper(*args, **kwargs):
            sigInfo = SigInfo(func, *args, **kwargs)

            for par_name, cls in pars_to_cast.items():
                if par_name not in sigInfo.names:
                    raise AttributeError(
                        f"Function does not have a `{par_name}` parameter.")
                if not typeChecker(sigInfo[par_name], cls, error=False):
                    sigInfo[par_name] = cls(sigInfo[par_name])

            return sigInfo.call()
예제 #11
0
    def getParentPartOrPage(self):
        """
        Get the parentPart of this part unless this part is topElement to it's parentPage, in which case parentPage is returned.

        :param generalgui.element.Element or generalgui.Page self: Element or Page
        """
        if typeChecker(self.parentPage, "App", error=False):
            return self.parentPage

        elif self.parentPage.topElement == self:
            return self.parentPage
        else:
            return self.parentPart
예제 #12
0
    def _checkEventForScrollTarget(self, event):
        if not event:
            return
        eventElement = event.widget.element
        if typeChecker(eventElement, "App", error=False):
            return

        pages = eventElement.getParents()
        for page in pages:
            if page.scrollable and page.mouseScroll:
                visibleFraction = self.getVisibleFraction(page.canvas)
                if not visibleFraction >= Vec2(1):
                    self.scrollWheelTarget = page.canvas
                    break
예제 #13
0
    def getTopElement(self):
        """
        Get top element from a part.

        :param generalgui.element.Element or generalgui.page.Page or generalgui.app.App self: Element, Page or App
        :rtype: generalgui.element.Element or generalgui.Frame
        """
        if typeChecker(self, ("App", "Element"), error=False):
            return self
        else:
            if self.topElement is None:
                return self.parentPage.getBaseElement()
            else:
                return self.topElement
예제 #14
0
    def createBind(self, key, func, add=True, name=None):
        """
        Add a function to a list in dict that is called with _bindCaller().
        If a bind exists with same name and key then it's overwritten.

        :param generalgui.Element or generalgui.Page or generalgui.App self:
        :param str key: A key from https://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
        :param function or None func: A function to be called or None to unbind
        :param bool add: Add to existing binds instead of overwriting
        :param str name: Name of bind, if bind with that name exists then it's replaced
        :return: Bind
        :raises NameError: If bind name exists with another key
        """
        if not add:
            self.removeBind(key)
        elif name:
            existingBind = self.getBindByName(name)
            if existingBind:
                if existingBind.key == key:
                    existingBind.remove()
                else:
                    raise NameError(
                        f"{existingBind} is already using this name with another key"
                    )

        bind = Bind(element=self, key=key, func=func, name=name)
        addToListInDict(self.events, key, bind)
        self.app.widgetBind(key)

        # print(typeChecker(self, "Element", error=False), key == "<Button-1>", self.styleHandler)

        if typeChecker(self, "Element",
                       error=False) and key == "<Button-1>" and (
                           not self.styleHandler
                           or "Hover" not in self.styleHandler.allStyles):
            self.widgetConfig(cursor="hand2")
            self.createStyle("Hover", "<Enter>", "<Leave>", bg="gray90")
            self.createStyle("Click",
                             "<Button-1>",
                             "<ButtonRelease-1>",
                             style="Hover",
                             relief="sunken",
                             fg="gray40")
            self.createBind("<Return>", self.click)

        return bind
예제 #15
0
    def _clickNextButton(self):
        """
        Click the first sibling that's a button when Enter key is pressed.

        :return: Click's return value or False if no button was found
        """
        for parentPage in self.getParents():

            if self.parentPage == parentPage:
                elements = self.getSiblings()
            else:
                elements = parentPage.getChildren(ignore=self)

            for element in elements:
                if typeChecker(element, "Button", error=False):
                    return element.click()
        return False
예제 #16
0
    def remove(self):
        """
        Remove a widget for good.

        :param generalgui.element.Element or generalgui.page.Page or generalgui.app.App self: Element, Page or App
        """
        self.removed = True

        for part in (self.getChildren(recurrent=True) +
                     self.getChildren(includeParts=True, recurrent=True)):
            part.removed = True

        if typeChecker(self, "App", error=False):
            self.getApps().remove(self)
            self.widget.quit()
        else:
            self.getTopWidget().update()
            self.getTopWidget().destroy()
예제 #17
0
    def rainbow(self, reset=False):
        """
        Give every widget and subwidget recursively a random background color.

        :param generalgui.element.Element or generalgui.page.Page or generalgui.app.App self: Element, Page or App
        :param reset:
        """

        if typeChecker(self, "Element", error=False):
            if reset:
                if self.styleHandler:
                    self.styleHandler.disable("Rainbow")
            else:
                self.createStyle("Rainbow",
                                 priority=0.1,
                                 bg=Vec.random(50, 255).hex()).enable()

        for element in self.getChildren(includeParts=True):
            element.rainbow(reset=reset)
예제 #18
0
    def bindCaller(self, event_or_element, key):
        """
        This method is bound to App's widget for any part key that is bound.
        It starts with event.widget.element and goes through all parents to call each method stored in self.events.
        It stops if it reaches App or a disabled propagation.

        :param generalgui.App self:
        :param any event_or_element:
        :param str key:
        """
        if not self.exists():
            return []

        if typeChecker(event_or_element, "Element", error=False):
            element = event_or_element
            event = None
        else:
            element = getattr(event_or_element.widget, "element", None)
            if element is None:
                return
            event = event_or_element
        if not element.exists():
            return []

        returns = []
        for part in element.getParents(includeSelf=True, includeApp=True):
            for bind in part.events.get(key, []):
                if leadingArgsCount(bind.func):
                    value = bind(event)
                else:
                    value = bind()

                if value is not None:
                    returns.append(value)

            if key in part.disabledPropagations:
                break
        return returns
예제 #19
0
    def createMenu(self, event_or_part):
        """
        Create a menu for a part, used by part.showMenu()

        :param generalgui.app.App self:
        :param event_or_part: Event filled by right clicking or part filled manually
        """
        if typeChecker(event_or_part, "event", error=False):
            self.menuTargetElement = event_or_part.widget.element
        else:
            self.menuTargetElement = event_or_part

        if self.menuPage:
            self.menuPage.remove()

        self.menuPage = self.Page(self,
                                  relief="solid",
                                  borderwidth=1,
                                  padx=5,
                                  pady=5)
        for part in self.menuTargetElement.getParents(includeSelf=True,
                                                      includeApp=True,
                                                      includeParts=True):
            if part.menuContent:
                self._addLine()
            for label, buttons in part.menuContent.items():
                self._addLabel(label)
                for buttonText, buttonFunc in buttons.items():
                    if buttonText.endswith(":"):
                        buttonValue = buttonFunc()
                        if buttonValue is not None:
                            self._addLabel(f"{buttonText} {buttonValue}")
                    else:
                        self._addButton(buttonText, buttonFunc)

        self.menuPage.place(self.getMouse())
예제 #20
0
    def __init__(self,
                 parentPage,
                 widgetClass,
                 pack=True,
                 makeBase=False,
                 resizeable=False,
                 onClick=None,
                 pos=None,
                 **parameters):
        Element_App.__init__(self)
        Element_Page_App.__init__(self)

        typeChecker(parentPage, "Page")

        if pos is not None:
            pos = Vec2(pos)
            parameters["column"] = pos.x
            parameters["row"] = pos.y

        self.app = parentPage.app
        self.parentPage = parentPage
        self.parentPart = parentPage.getBaseElement()
        parameters["master"] = self.parentPart.widget

        # Extract initialization arguments from parameters
        initArgs = []
        signature = inspect.signature(widgetClass)
        for parameterName in signature.parameters:
            parameter = signature.parameters[parameterName]
            kind = str(parameter.kind)
            if kind == "VAR_KEYWORD":
                break
            if parameterName in parameters:
                if kind == "VAR_POSITIONAL":
                    initArgs.extend(parameters[parameterName])
                else:
                    initArgs.append(parameters[parameterName])
                del parameters[parameterName]
            elif parameter.default != inspect.Parameter.empty:
                initArgs.append(parameter.default)
            elif kind == "VAR_POSITIONAL":
                break
            else:
                raise AttributeError(
                    f"Missing positional parameter that doesn't have a default value {parameterName} with kind {kind}"
                )

        self.parameters = parameters
        self.widget = widgetClass(*initArgs)
        self.baseFor = None

        setattr(self.widget, "element", self)

        configParameters = {}
        self.packParameters = {}
        allConfigKeys = self.getAllWidgetConfigs()
        for key, value in parameters.items():
            if key in allConfigKeys:
                configParameters[key] = value
            else:
                self.packParameters[key] = value
        self.widgetConfig(**configParameters)

        if makeBase:
            self.makeBase()
        if pack:
            self.pack()
        if resizeable:
            self.resizeable()
        if onClick:
            self.onClick(onClick)
예제 #21
0
    def fillGrid(self,
                 eleCls,
                 start,
                 size,
                 values=None,
                 removeExcess=False,
                 color=False,
                 **parameters):
        """
        Fill grid with values, using a start position and a size.
        If there already is an element in the cell then it's re-used, unless value is an Element.

        :param class eleCls: Class to be created in each cell unless value is an Element
        :param Vec2 start: Start position
        :param Vec2 size: Size of values as Vec2, needs to match values len
        :param list[str or generalgui.element.Element] values: Values to be given to object as 'value' parameter
        :param removeExcess: Whether to remove cells with a greater position than fill area
        :param color: Whether to color alternating rows
        :param parameters: Parameters to be given to objects
        """
        if eleCls == Label and "anchor" not in parameters:
            parameters["anchor"] = "w"

        currentSize = self.getGridSize()
        maxSize = currentSize.max(start + size)
        fillRange = start.range(size)

        if values is not None:
            values = list(values)
            if len(values) != len(fillRange):
                raise ValueError("Values length doesn't match fillRange's")

        for pos in Vec2(0).range(maxSize):
            # Create cell with pos and values[0]
            if fillRange and pos == fillRange[0]:

                value = values[0] if values else None
                valueIsElement = typeChecker(
                    value, "Element",
                    error=False) and value.__class__.__name__ != "type"
                existingElement = self.getGridElement(pos)

                # debug(locals(), "eleCls", "pos", "value", "valueIsElement", "existingElement")

                if valueIsElement:
                    element = value
                    if element.parentPage != self:
                        raise AttributeError(
                            f"{element}'s parentPage has to be grid {self}")

                    if existingElement:
                        self._removeOrHideEle(values, existingElement)

                    element.grid(pos)

                else:
                    if existingElement:
                        sameCls = typeChecker(existingElement,
                                              eleCls,
                                              error=False)
                        canSetValue = hasattr(existingElement, "setValue")

                        # debug(locals(), "existingElement", "sameCls", "canSetValue", "value")

                        if sameCls and (canSetValue or value is None):
                            if value is not None:
                                existingElement.setValue(value)
                        else:
                            self._removeOrHideEle(values, existingElement)
                            existingElement = None

                    if not existingElement:
                        if color and pos.y:
                            parameters["bg"] = None if pos.y % 2 else "gray88"
                        element = eleCls(self,
                                         column=pos.x,
                                         row=pos.y,
                                         value=value,
                                         **parameters)

                del fillRange[0]
                if values:
                    del values[0]

            elif removeExcess and not pos <= start + size - 1:
                if element := self.getGridElement(pos):
                    element.remove()