diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 55cf087..ff1ea65 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -31,7 +31,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install wheel twine + pip install setuptools wheel twine - name: Build distributions run: | diff --git a/Documentation/source/conf.py b/Documentation/source/conf.py index d8c8b40..7e9bcfa 100644 --- a/Documentation/source/conf.py +++ b/Documentation/source/conf.py @@ -30,7 +30,11 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx_rtd_theme'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx_rtd_theme', + 'sphinx.ext.todo' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -227,6 +231,15 @@ def __call__(self, *args, **kwargs): def __getattr__(cls, name): if name in ('__file__', '__path__'): return '/dev/null' + elif name == "NSObject": + return object + elif name == "python_method": + def dummyDecorator(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + wrapper.__doc__ = func.__doc__ + return wrapper + return dummyDecorator else: return Mock diff --git a/Documentation/source/index.rst b/Documentation/source/index.rst index 6e8ea76..057fce5 100644 --- a/Documentation/source/index.rst +++ b/Documentation/source/index.rst @@ -21,6 +21,7 @@ Windows objects/Window objects/FloatingWindow + objects/ModalWindow objects/Sheet objects/Drawer diff --git a/Documentation/source/objects/FloatingWindow.rst b/Documentation/source/objects/FloatingWindow.rst index eaebbcb..4b8c482 100644 --- a/Documentation/source/objects/FloatingWindow.rst +++ b/Documentation/source/objects/FloatingWindow.rst @@ -9,194 +9,6 @@ FloatingWindow :inherited-members: :members: -.. method:: FloatingWindow.assignToDocument(document) - - Add this window to the list of windows associated with a document. - - **document** should be a *NSDocument* instance. - - -.. method:: FloatingWindow.setTitle(title) - - Set the title in the window's title bar. - - **title** should be a string. - - -.. method:: FloatingWindow.setPosSize(posSize, animate=True) - - Set the position and size of the FloatingWindow. - - **posSize** A tuple of form *(left, top, width, height)*. - - -.. method:: FloatingWindow.move(x, y, animate=True) - - Move the window by *x* units and *y* units. - - -.. method:: FloatingWindow.resize(width, height, animate=True) - - Change the size of the window to *width* and *height*. - - -.. method:: FloatingWindow.setDefaultButton(button) - - Set the default button in the FloatingWindow. - - **button** will be bound to the Return and Enter keys. - - -.. method:: FloatingWindow.bind(event, callback) - - Bind a callback to an event. - - **event** A string representing the desired event. The options are: - - +-------------------+----------------------------------------------------------------------+ - | *"should close"* | Called when the user attempts to close the window. This must return | - | | a bool indicating if the window should be closed or not. | - +-------------------+----------------------------------------------------------------------+ - | *"close"* | Called immediately before the window closes. | - +-------------------+----------------------------------------------------------------------+ - | *"move"* | Called immediately after the window is moved. | - +-------------------+----------------------------------------------------------------------+ - | *"resize"* | Called immediately after the window is resized. | - +-------------------+----------------------------------------------------------------------+ - | *"became main"* | Called immediately after the window has become the main window. | - +-------------------+----------------------------------------------------------------------+ - | *"resigned main"* | Called immediately after the window has lost its main window status. | - +-------------------+----------------------------------------------------------------------+ - | *"became key"* | Called immediately after the window has become the key window. | - +-------------------+----------------------------------------------------------------------+ - | *"resigned key"* | Called immediately after the window has lost its key window status. | - +-------------------+----------------------------------------------------------------------+ - - For more information about main and key windows, refer to the Cocoa `documentation`_ on the subject. - - .. _documentation: http://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/WinPanel/Concepts/ChangingMainKeyFloatingWindow.html - - **callback** The callback that will be called when the event occurs. It should accept a *sender* argument which will - be the Window that called the callback.:: - - from vanilla import Window - - class WindowBindDemo(object): - - def __init__(self): - self.w = Window((200, 200)) - self.w.bind("move", self.windowMoved) - self.w.open() - - def windowMoved(self, sender): - print("window moved!", sender) - - WindowBindDemo() - - -.. method:: FloatingWindow.unbind(event, callback) - - Unbind a callback from an event. - - **event** A string representing the desired event. - Refer to *bind* for the options. - - **callback** The callback that has been bound to the event. - - -.. method:: FloatingWindow.addToolbar(toolbarIdentifier, toolbarItems, addStandardItems=True) - - Add a toolbar to the FloatingWindow. - - **toolbarIdentifier** A string representing a unique name for the toolbar. - - **toolbarItems** An ordered list of dictionaries containing the following items: - - +-------------------------------+---------------------------------------------------------------------------+ - | *itemIdentifier* | A unique string identifier for the item. This is only used internally. | - +-------------------------------+---------------------------------------------------------------------------+ - | *label* (optional) | The text label for the item. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *paletteLabel* (optional) | The text label shown in the customization palette. Defaults to *label*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *toolTip* (optional) | The tool tip for the item. Defaults to *label*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imagePath* (optional) | A file path to an image. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageNamed* (optional) | The name of an image already loaded as a `NSImage`_ by the application. | - | | Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageObject* (optional) | A `NSImage`_ object. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageTemplate* (optional) | A boolean representing if the image should converted to a template image. | - +-------------------------------+---------------------------------------------------------------------------+ - | *selectable* (optional) | A boolean representing if the item is selectable or not. The default | - | | value is *False*. For more information on selectable toolbar items, refer | - | | to Apple's documentation. | - +-------------------------------+---------------------------------------------------------------------------+ - | *view* (optional) | A *NSView* object to be used instead of an image. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *visibleByDefault* (optional) | If the item should be visible by default pass *True* to this argument. | - | | If the item should be added to the toolbar only through the customization | - | | palette, use a value of *False*. Defaults to *True*. | - +-------------------------------+---------------------------------------------------------------------------+ - - .. _NSImage: http://developer.apple.com/documentation/appkit/nsimage?language=objc - - **addStandardItems** A boolean, specifying whether the standard Cocoa toolbar items - should be added. Defaults to *True*. If you set it to *False*, you must specify any - standard items manually in *toolbarItems*, by using the constants from the AppKit module: - - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarSeparatorItemIdentifier* | The Separator item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarSpaceItemIdentifier* | The Space item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarFlexibleSpaceItemIdentifier* | The Flexible Space item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarShowColorsItemIdentifier* | The Colors item. Shows the color panel. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarShowFontsItemIdentifier* | The Fonts item. Shows the font panel. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarCustomizeToolbarItemIdentifier* | The Customize item. Shows the customization palette. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarPrintItemIdentifier* | The Print item. Refer to Apple's `NSToolbarItem`_ | - | | documentation for more information. | - +-------------------------------------------+------------------------------------------------------+ - - .. _NSToolbarItem: https://developer.apple.com/documentation/appkit/nstoolbaritem?language=objc - - **displayMode** A string representing the desired display mode for the toolbar. - - +-------------+ - | "default" | - +-------------+ - | "iconLabel" | - +-------------+ - | "icon" | - +-------------+ - | "label" | - +-------------+ - - **sizeStyle** A string representing the desired size for the toolbar - - +-----------+ - | "default" | - +-----------+ - | "regular" | - +-----------+ - | "small" | - +-----------+ - - Returns a dictionary containing the created toolbar items, mapped by itemIdentifier. - - -.. method:: FloatingWindow.removeToolbarItem(itemIdentifier) - - Remove a toolbar item by his identifier. - - **itemIdentifier** A unique string identifier for the removed item. - .. autoclass:: HUDFloatingWindow :members: diff --git a/Documentation/source/objects/List.rst b/Documentation/source/objects/List.rst index bc21f54..bc8a493 100644 --- a/Documentation/source/objects/List.rst +++ b/Documentation/source/objects/List.rst @@ -1,22 +1,21 @@ .. highlight:: python -===== -List2 -===== +==== +List +==== -.. module:: vanilla2 +.. module:: vanilla .. autoclass:: List :inherited-members: :members: -================ -List2 Item Cells -================ +=============== +List Item Cells +=============== -.. autofunction:: EditTextList2Cell -.. autofunction:: CheckBoxList2Cell -.. autofunction:: SliderList2Cell -.. autofunction:: PopUpButtonList2Cell -.. autofunction:: ImageList2Cell -.. autofunction:: SegmentedButtonList2Cell -.. autofunction:: ColorWellList2Cell +.. autofunction:: CheckBoxListCell +.. autofunction:: SliderListCell +.. autofunction:: PopUpButtonListCell +.. autofunction:: ImageListCell +.. autofunction:: SegmentedButtonListCell +.. autofunction:: LevelIndicatorListCell diff --git a/Documentation/source/objects/List2.rst b/Documentation/source/objects/List2.rst index bc8a493..201927c 100644 --- a/Documentation/source/objects/List2.rst +++ b/Documentation/source/objects/List2.rst @@ -1,21 +1,22 @@ .. highlight:: python -==== -List -==== +===== +List2 +===== .. module:: vanilla -.. autoclass:: List +.. autoclass:: List2 :inherited-members: :members: -=============== -List Item Cells -=============== +================ +List2 Item Cells +================ -.. autofunction:: CheckBoxListCell -.. autofunction:: SliderListCell -.. autofunction:: PopUpButtonListCell -.. autofunction:: ImageListCell -.. autofunction:: SegmentedButtonListCell -.. autofunction:: LevelIndicatorListCell +.. autofunction:: EditTextList2Cell +.. autofunction:: CheckBoxList2Cell +.. autofunction:: SliderList2Cell +.. autofunction:: PopUpButtonList2Cell +.. autofunction:: ImageList2Cell +.. autofunction:: SegmentedButtonList2Cell +.. autofunction:: ColorWellList2Cell diff --git a/Documentation/source/objects/ModalWindow.rst b/Documentation/source/objects/ModalWindow.rst index 62e73c7..c455b02 100644 --- a/Documentation/source/objects/ModalWindow.rst +++ b/Documentation/source/objects/ModalWindow.rst @@ -8,3 +8,10 @@ ModalWindow .. autoclass:: ModalWindow :inherited-members: :members: + + +.. module:: vanilla +.. autoclass:: vanilla.vanillaWindows.VModalWindow + :inherited-members: + :members: + diff --git a/Documentation/source/objects/Window.rst b/Documentation/source/objects/Window.rst index 520a70e..b85915f 100644 --- a/Documentation/source/objects/Window.rst +++ b/Documentation/source/objects/Window.rst @@ -9,191 +9,3 @@ Window :inherited-members: :members: - -.. method:: Window.assignToDocument(document) - - Add this window to the list of windows associated with a document. - - **document** should be a *NSDocument* instance. - - -.. method:: Window.setTitle(title) - - Set the title in the window's title bar. - - **title** should be a string. - - -.. method:: Window.setPosSize(posSize, animate=True) - - Set the position and size of the window. - - **posSize** A tuple of form *(left, top, width, height)*. - - -.. method:: Window.move(x, y, animate=True) - - Move the window by *x* units and *y* units. - - -.. method:: Window.resize(width, height, animate=True) - - Change the size of the window to *width* and *height*. - - -.. method:: Window.setDefaultButton(button) - - Set the default button in the window. - - **button** will be bound to the Return and Enter keys. - - -.. method:: Window.bind(event, callback) - - Bind a callback to an event. - - **event** A string representing the desired event. The options are: - - +-------------------+----------------------------------------------------------------------+ - | *"should close"* | Called when the user attempts to close the window. This must return | - | | a bool indicating if the window should be closed or not. | - +-------------------+----------------------------------------------------------------------+ - | *"close"* | Called immediately before the window closes. | - +-------------------+----------------------------------------------------------------------+ - | *"move"* | Called immediately after the window is moved. | - +-------------------+----------------------------------------------------------------------+ - | *"resize"* | Called immediately after the window is resized. | - +-------------------+----------------------------------------------------------------------+ - | *"became main"* | Called immediately after the window has become the main window. | - +-------------------+----------------------------------------------------------------------+ - | *"resigned main"* | Called immediately after the window has lost its main window status. | - +-------------------+----------------------------------------------------------------------+ - | *"became key"* | Called immediately after the window has become the key window. | - +-------------------+----------------------------------------------------------------------+ - | *"resigned key"* | Called immediately after the window has lost its key window status. | - +-------------------+----------------------------------------------------------------------+ - - For more information about main and key windows, refer to the Cocoa `documentation`_ on the subject. - - .. _documentation: http://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/WinPanel/Concepts/ChangingMainKeyWindow.html - - **callback** The callback that will be called when the event occurs. It should accept a *sender* argument which will - be the Window that called the callback.:: - - from vanilla import Window - - class WindowBindDemo(object): - - def __init__(self): - self.w = Window((200, 200)) - self.w.bind("move", self.windowMoved) - self.w.open() - - def windowMoved(self, sender): - print("window moved!", sender) - - WindowBindDemo() - - -.. method:: Window.unbind(event, callback) - - Unbind a callback from an event. - - **event** A string representing the desired event. - Refer to *bind* for the options. - - **callback** The callback that has been bound to the event. - - -.. method:: Window.addToolbar(toolbarIdentifier, toolbarItems, addStandardItems=True) - - Add a toolbar to the window. - - **toolbarIdentifier** A string representing a unique name for the toolbar. - - **toolbarItems** An ordered list of dictionaries containing the following items: - - +-------------------------------+---------------------------------------------------------------------------+ - | *itemIdentifier* | A unique string identifier for the item. This is only used internally. | - +-------------------------------+---------------------------------------------------------------------------+ - | *label* (optional) | The text label for the item. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *paletteLabel* (optional) | The text label shown in the customization palette. Defaults to *label*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *toolTip* (optional) | The tool tip for the item. Defaults to *label*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imagePath* (optional) | A file path to an image. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageNamed* (optional) | The name of an image already loaded as a `NSImage`_ by the application. | - | | Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageObject* (optional) | A `NSImage`_ object. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *imageTemplate* (optional) | A boolean representing if the image should converted to a template image. | - +-------------------------------+---------------------------------------------------------------------------+ - | *selectable* (optional) | A boolean representing if the item is selectable or not. The default | - | | value is *False*. For more information on selectable toolbar items, refer | - | | to Apple's documentation. | - +-------------------------------+---------------------------------------------------------------------------+ - | *view* (optional) | A *NSView* object to be used instead of an image. Defaults to *None*. | - +-------------------------------+---------------------------------------------------------------------------+ - | *visibleByDefault* (optional) | If the item should be visible by default pass *True* to this argument. | - | | If the item should be added to the toolbar only through the customization | - | | palette, use a value of *False*. Defaults to *True*. | - +-------------------------------+---------------------------------------------------------------------------+ - - .. _NSImage: http://developer.apple.com/documentation/appkit/nsimage?language=objc - - **addStandardItems** A boolean, specifying whether the standard Cocoa toolbar items - should be added. Defaults to *True*. If you set it to *False*, you must specify any - standard items manually in *toolbarItems*, by using the constants from the AppKit module: - - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarSeparatorItemIdentifier* | The Separator item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarSpaceItemIdentifier* | The Space item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarFlexibleSpaceItemIdentifier* | The Flexible Space item. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarShowColorsItemIdentifier* | The Colors item. Shows the color panel. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarShowFontsItemIdentifier* | The Fonts item. Shows the font panel. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarCustomizeToolbarItemIdentifier* | The Customize item. Shows the customization palette. | - +-------------------------------------------+------------------------------------------------------+ - | *NSToolbarPrintItemIdentifier* | The Print item. Refer to Apple's `NSToolbarItem`_ | - | | documentation for more information. | - +-------------------------------------------+------------------------------------------------------+ - - .. _NSToolbarItem: https://developer.apple.com/documentation/appkit/nstoolbaritem?language=objc - - **displayMode** A string representing the desired display mode for the toolbar. - - +-------------+ - | "default" | - +-------------+ - | "iconLabel" | - +-------------+ - | "icon" | - +-------------+ - | "label" | - +-------------+ - - **sizeStyle** A string representing the desired size for the toolbar - - +-----------+ - | "default" | - +-----------+ - | "regular" | - +-----------+ - | "small" | - +-----------+ - - Returns a dictionary containing the created toolbar items, mapped by itemIdentifier. - - -.. method:: Window.removeToolbarItem(itemIdentifier) - - Remove a toolbar item by his identifier. - - **itemIdentifier** A unique string identifier for the removed item. diff --git a/Lib/vanilla/__init__.py b/Lib/vanilla/__init__.py index 4b7197f..982cc76 100644 --- a/Lib/vanilla/__init__.py +++ b/Lib/vanilla/__init__.py @@ -13,7 +13,20 @@ from vanilla.vanillaImageView import ImageView from vanilla.vanillaLevelIndicator import LevelIndicator, LevelIndicatorListCell from vanilla.vanillaList import List, CheckBoxListCell, SliderListCell, PopUpButtonListCell, ImageListCell, SegmentedButtonListCell -from vanilla.vanillaList2 import List2, List2GroupRow, EditTextList2Cell, GroupTitleList2Cell, SliderList2Cell, CheckBoxList2Cell, PopUpButtonList2Cell, ImageList2Cell, SegmentedButtonList2Cell, ColorWellList2Cell, LevelIndicatorList2Cell +from vanilla.vanillaList2 import ( + List2, + List2GroupRow, + EditTextList2Cell, + GroupTitleList2Cell, + SliderList2Cell, + CheckBoxList2Cell, + PopUpButtonList2Cell, + ImageList2Cell, + SegmentedButtonList2Cell, + ColorWellList2Cell, + LevelIndicatorList2Cell, + ComboBoxList2Cell +) from vanilla.vanillaPathControl import PathControl from vanilla.vanillaPopUpButton import PopUpButton, ActionButton from vanilla.vanillaPopover import Popover @@ -49,7 +62,7 @@ "ImageView", "LevelIndicator", "LevelIndicatorListCell", "List", "CheckBoxListCell", "SliderListCell", "PopUpButtonListCell", "ImageListCell", "SegmentedButtonListCell", - "List2", "List2GroupRow", "EditTextList2Cell", "GroupTitleList2Cell", "SliderList2Cell", "CheckBoxList2Cell", "PopUpButtonList2Cell", "ImageList2Cell", "SegmentedButtonList2Cell", "ColorWellList2Cell", + "List2", "List2GroupRow", "EditTextList2Cell", "GroupTitleList2Cell", "SliderList2Cell", "CheckBoxList2Cell", "PopUpButtonList2Cell", "ImageList2Cell", "SegmentedButtonList2Cell", "ColorWellList2Cell", "ComboBoxList2Cell", "ObjectBrowser", "PathControl", "PopUpButton", "ActionButton", diff --git a/Lib/vanilla/test/list2/cells.py b/Lib/vanilla/test/list2/cells.py index 4fee07b..5d6a362 100644 --- a/Lib/vanilla/test/list2/cells.py +++ b/Lib/vanilla/test/list2/cells.py @@ -13,6 +13,8 @@ def __init__(self): d = dict( textField="ABC", editableTextField="XYZ", + comboBox="AAA", + editableComboBox="BBB", slider=25, editableSlider=75, checkBox=True, @@ -46,6 +48,26 @@ def __init__(self): width=75, editable=True ), + dict( + identifier="comboBox", + title="ComboBox", + width=75, + cellClass=vanilla.ComboBoxList2Cell, + cellClassArguments=dict( + items=["AAA", "BBB", "CCC"] + ), + editable=False + ), + dict( + identifier="editableComboBox", + title="ComboBox-E", + width=75, + cellClass=vanilla.ComboBoxList2Cell, + cellClassArguments=dict( + items=["AAA", "BBB", "CCC"] + ), + editable=True + ), dict( identifier="slider", title="Slider", diff --git a/Lib/vanilla/test/list2/columns.py b/Lib/vanilla/test/list2/columns.py new file mode 100644 index 0000000..9fd1df4 --- /dev/null +++ b/Lib/vanilla/test/list2/columns.py @@ -0,0 +1,96 @@ +import pprint +import vanilla + + +def makeItems(): + items = [] + letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + for i, letter in enumerate(letters): + number = i + 1 + item = dict( + letter=letter, + number=number + ) + items.append(item) + return items + + +class Test: + + def __init__(self): + self.w = vanilla.Window((500, 300)) + self.w.line = vanilla.VerticalLine("auto") + + columnDescriptions = [ + dict( + identifier="letter", + title="Letter", + sortable=True + ), + dict( + identifier="number", + title="Number", + sortable=True, + ) + ] + self.w.list = vanilla.List2( + "auto", + items=makeItems(), + columnDescriptions=columnDescriptions, + ) + self.w.appendColumnButton = vanilla.Button( + "auto", + "Append Column", + callback=self.appendColumnButtonCallback + ) + self.w.removeColumnButton = vanilla.Button( + "auto", + "Remove Column", + callback=self.removeColumnButtonCallback + ) + self.w.getButton = vanilla.Button( + "auto", + "Get Values", + callback=self.getButtonCallback + ) + rules = [ + "H:|-[list]-|", + "H:[getButton]-[appendColumnButton]-[removeColumnButton]-|", + "V:|-[list]-[appendColumnButton]-|", + "V:|-[list]-[removeColumnButton]-|", + "V:|-[list]-[getButton]-|" + ] + self.w.addAutoPosSizeRules(rules) + self.w.open() + + def appendColumnButtonCallback(self, sender): + try: + self.w.list.insertColumn( + 1, + dict( + identifier="punctuation", + title="Punctuation", + sortable=True, + editable=True + ), + ) + except Exception as e: + print(e) + + def removeColumnButtonCallback(self, sender): + self.w.list.removeColumn( + "punctuation" + ) + + def getButtonCallback(self, sender): + for i, item in enumerate(self.w.list.get()): + print(f"{i} ----------") + for k, v in item.items(): + v = pprint.pformat(v) + print(f"{k} : {v}") + print("\n") + + +if __name__ == "__main__": + from vanilla.test.testTools import executeVanillaTest + executeVanillaTest(Test) diff --git a/Lib/vanilla/test/testAll.py b/Lib/vanilla/test/testAll.py index 482ea1d..ae447b5 100644 --- a/Lib/vanilla/test/testAll.py +++ b/Lib/vanilla/test/testAll.py @@ -308,7 +308,7 @@ def __init__(self, drawGrid=False): simpleList = List((0, 0, 0, 0), listOptions, enableTypingSensitivity=True) multiItems = [ - {"name": name, "path": os.path.basename(getattr(module, "__file__", "Unknown"))} + {"name": name, "path": os.path.basename(getattr(module, "__file__", None) or "Unknow")} for name, module in sys.modules.items() ] columnDescriptions = [ diff --git a/Lib/vanilla/vanillaBrowser.py b/Lib/vanilla/vanillaBrowser.py index 1224875..681a52c 100644 --- a/Lib/vanilla/vanillaBrowser.py +++ b/Lib/vanilla/vanillaBrowser.py @@ -163,7 +163,8 @@ def getArguments(obj): and leave 'self' out. """ try: - arguments = inspect.formatargspec(*inspect.getargspec(obj)) + sig = inspect.signature(obj) + arguments = ", ".join(sig.parameters.keys()) except TypeError: arguments = "" return arguments.replace("self, ", "").replace("self", "") diff --git a/Lib/vanilla/vanillaColorWell.py b/Lib/vanilla/vanillaColorWell.py index f28cdc9..77c7906 100644 --- a/Lib/vanilla/vanillaColorWell.py +++ b/Lib/vanilla/vanillaColorWell.py @@ -1,4 +1,5 @@ from AppKit import NSColorWell, NSColorPanel +from objc import super from vanilla.vanillaBase import VanillaBaseObject, osVersionCurrent, osVersion13_0 if osVersionCurrent >= osVersion13_0: diff --git a/Lib/vanilla/vanillaComboBox.py b/Lib/vanilla/vanillaComboBox.py index 52cc053..dcce66f 100644 --- a/Lib/vanilla/vanillaComboBox.py +++ b/Lib/vanilla/vanillaComboBox.py @@ -24,7 +24,11 @@ def controlTextDidEndEditing_(self, notification): def comboBoxSelectionDidChange_(self, notification): obj = notification.object() - obj.setObjectValue_(obj.objectValueOfSelectedItem()) + if obj.usesDataSource(): + value = obj.dataSource().comboBox_objectValueForItemAtIndex_(obj, obj.indexOfSelectedItem()) + else: + value = obj.objectValueOfSelectedItem() + obj.setObjectValue_(value) self._callVanillaCallback(notification) diff --git a/Lib/vanilla/vanillaGridView.py b/Lib/vanilla/vanillaGridView.py index 4a7f04b..2b4b93e 100644 --- a/Lib/vanilla/vanillaGridView.py +++ b/Lib/vanilla/vanillaGridView.py @@ -533,6 +533,12 @@ def removeRow(self, index): Remove row at *index*. """ gridView = self.getNSGridView() + # XXX + # removeRowAtIndex_ doesn't remove the + # visualization of the row, but the row + # object is gone. to hack around this, + # hide the row before removing it. + self.showRow(index, False) gridView.removeRowAtIndex_(index) def moveRow(self, fromIndex, toIndex): diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index d61385f..65c6482 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -34,6 +34,7 @@ def initWithTableView_(self, tableView): self._cellToValueConverters = {} # { identifier : function } self._groupRowCellClass = None self._groupRowCellClassKwargs = {} + self._editedRowIndex = None self._cellWrappers = {} # { nsView : vanilla wrapper } for view + wrapper reuse purposes self._valueGetters = {} # { identifier : options (see below) } self._valueSetters = {} # { identifier : options (see below) } @@ -56,20 +57,40 @@ def setGroupCellClassWithKwargs(self, cls, kwargs): self._groupRowCellClassKwargs = kwargs @python_method - def setGetters(self, getters): - self._valueGetters = getters + def addGetter(self, identifier, getter): + self._valueGetters[identifier] = getter @python_method - def setSetters(self, setters): - self._valueSetters = setters + def removeGetter(self, identifier): + if identifier in self._valueGetters: + del self._valueGetters[identifier] @python_method - def setValueToCellConverters(self, converters): - self._valueToCellConverters = converters + def addSetter(self, identifier, setter): + self._valueSetters[identifier] = setter @python_method - def setCellToValueConverters(self, converters): - self._cellToValueConverters = converters + def removeSetter(self, identifier): + if identifier in self._valueGetters: + del self._valueSetters[identifier] + + @python_method + def addValueToCellConverters(self, identifier, converter): + self._valueToCellConverters[identifier] = converter + + @python_method + def removeValueToCellConverters(self, identifier): + if identifier in self._valueToCellConverters: + del self._valueToCellConverters[identifier] + + @python_method + def addCellToValueConverters(self, identifier, converter): + self._cellToValueConverters[identifier] = converter + + @python_method + def removeCellToValueConverters(self, identifier): + if identifier in self._cellToValueConverters: + del self._cellToValueConverters[identifier] @python_method def vanillaWrapper(self): @@ -156,7 +177,7 @@ def getItemValueForColumnAndRow(self, identifier, row): elif function is not None: value = function(item) else: - value = item[identifier] + value = item.get(identifier, "") if identifier in self._valueToCellConverters: value = self._valueToCellConverters[identifier](value) return value @@ -178,6 +199,7 @@ def setItemValueForColumnAndRow(self, value, identifier, row): return function(item, value) elif isinstance(item, dict): item[identifier] = value + return value # Data Source @@ -244,10 +266,18 @@ def tableView_shouldSelectRow_(self, tableView, row): def cellEditCallback(self, sender): identifier, row = sender._representedColumnRow value = sender.get() - self.setItemValueForColumnAndRow(value, identifier, row) + editedValue = self.setItemValueForColumnAndRow(value, identifier, row) + if identifier in self._valueToCellConverters: + sender.set(self._valueToCellConverters[identifier](editedValue)) wrapper = self.vanillaWrapper() + self._editedRowIndex = row if wrapper._editCallback is not None: wrapper._editCallback(wrapper) + self._editedRowIndex = None + + @python_method + def getEditedRowIndex(self): + return self._editedRowIndex # Drag @@ -295,7 +325,11 @@ def tableView_acceptDrop_row_dropOperation_( class VanillaList2TableViewSubclass(AppKit.NSTableView): def keyDown_(self, event): - didSomething = self.vanillaWrapper()._keyDown(event) + wrapper = self.vanillaWrapper() + if wrapper is None: + didSomething = False + else: + didSomething = wrapper._keyDown(event) if not didSomething: super().keyDown_(event) @@ -627,84 +661,131 @@ def _getDropView(self): return self._tableView def _buildColumns(self, columnDescriptions): - getters = {} - setters = {} - cellToValueConverters = {} - valueToCellConverters = {} - rowHeights = [] if osVersionCurrent >= osVersion10_16: - rowHeights.append(24) + self._tableView.setRowHeight_(24) else: - rowHeights.append(17) + self._tableView.setRowHeight_(17) for columnDescription in columnDescriptions: - identifier = columnDescription["identifier"] - title = columnDescription.get("title", "") - width = columnDescription.get("width") - minWidth = columnDescription.get("minWidth", width) - maxWidth = columnDescription.get("maxWidth", width) - sortable = columnDescription.get("sortable") - cellClass = columnDescription.get("cellClass", EditTextList2Cell) - cellKwargs = columnDescription.get("cellClassArguments", {}) - editable = columnDescription.get("editable", False) - property = columnDescription.get("property") - getMethod = columnDescription.get("getMethod") - setMethod = columnDescription.get("setMethod") - getFunction = columnDescription.get("getFunction") - setFunction = columnDescription.get("setFunction") - cellToValueConverter = columnDescription.get("cellToValueConverter") - valueToCellConverter = columnDescription.get("valueToCellConverter") - getters[identifier] = dict( + self.appendColumn(columnDescription) + + def getColumnIdentifiers(self): + """ + Return a list of column identifiers. + """ + return [column.identifier() for column in self._tableView.tableColumns()] + + def appendColumn(self, columnDescription): + """ + Append a column discription. + The column identifier has to be unique for this table. + """ + self.insertColumn(-1, columnDescription) + + def removeColumn(self, identifier): + """ + Remove a column by the `identifier`. + This will fail silently when the column does not exists. + """ + column = self._tableView.tableColumnWithIdentifier_(identifier) + if column: + self._tableView.removeTableColumn_(column) + self._dataSourceAndDelegate.removeGetter(identifier) + self._dataSourceAndDelegate.removeSetter(identifier) + self._dataSourceAndDelegate.removeValueToCellConverters(identifier) + self._dataSourceAndDelegate.removeCellToValueConverters(identifier) + + def insertColumn(self, index, columnDescription): + """ + Insert a column discription and an index. + The column identifier has to be unique for this table. + """ + identifier = columnDescription["identifier"] + # dont allow columns with the same identifier + assert not self._tableView.tableColumnWithIdentifier_(identifier), f"Column with identifier '{identifier}' already exists." + title = columnDescription.get("title", "") + width = columnDescription.get("width") + minWidth = columnDescription.get("minWidth", width) + maxWidth = columnDescription.get("maxWidth", width) + sortable = columnDescription.get("sortable") + cellClass = columnDescription.get("cellClass", EditTextList2Cell) + cellKwargs = columnDescription.get("cellClassArguments", {}) + editable = columnDescription.get("editable", False) + property = columnDescription.get("property") + getMethod = columnDescription.get("getMethod") + setMethod = columnDescription.get("setMethod") + getFunction = columnDescription.get("getFunction") + setFunction = columnDescription.get("setFunction") + cellToValueConverter = columnDescription.get("cellToValueConverter") + valueToCellConverter = columnDescription.get("valueToCellConverter") + + self._dataSourceAndDelegate.addGetter( + identifier, + dict( property=property, method=getMethod, function=getFunction ) - setters[identifier] = dict( + ) + self._dataSourceAndDelegate.addSetter( + identifier, + dict( property=property, method=setMethod, function=setFunction ) - if cellToValueConverter is not None: - cellToValueConverters[identifier] = cellToValueConverter - if valueToCellConverter is not None: - valueToCellConverters[identifier] = valueToCellConverter - cellKwargs["editable"] = editable - if editable: - cellKwargs["callback"] = True - self._dataSourceAndDelegate.setCellClassWithKwargsForColumn( - cellClass, cellKwargs, identifier + ) + + if cellToValueConverter is not None: + self._dataSourceAndDelegate.addCellToValueConverters( + identifier, + cellToValueConverter + ) + if valueToCellConverter is not None: + self._dataSourceAndDelegate.addValueToCellConverters( + identifier, + valueToCellConverter ) - if width is not None: - if width == minWidth and width == maxWidth: - resizingMask = AppKit.NSTableColumnNoResizing - else: - resizingMask = AppKit.NSTableColumnUserResizingMask | AppKit.NSTableColumnAutoresizingMask + + cellKwargs["editable"] = editable + if editable: + cellKwargs["callback"] = True + self._dataSourceAndDelegate.setCellClassWithKwargsForColumn( + cellClass, cellKwargs, identifier + ) + if width is not None: + if width == minWidth and width == maxWidth: + resizingMask = AppKit.NSTableColumnNoResizing else: resizingMask = AppKit.NSTableColumnUserResizingMask | AppKit.NSTableColumnAutoresizingMask - column = AppKit.NSTableColumn.alloc().initWithIdentifier_(identifier) - column.setTitle_(title) - column.setResizingMask_(resizingMask) - if width is not None: - column.setWidth_(width) - column.setMinWidth_(minWidth) - column.setMaxWidth_(maxWidth) - if self._allowsSorting and sortable: - sortDescriptor = AppKit.NSSortDescriptor.sortDescriptorWithKey_ascending_selector_( - identifier, - True, - "compare:" - ) - column.setSortDescriptorPrototype_(sortDescriptor) - self._tableView.addTableColumn_(column) - # measure the cell to get the row height - cell = cellClass(**cellKwargs) - height = cell._nsObject.fittingSize().height - del cell - rowHeights.append(height) - self._dataSourceAndDelegate.setGetters(getters) - self._dataSourceAndDelegate.setSetters(setters) - self._dataSourceAndDelegate.setCellToValueConverters(cellToValueConverters) - self._dataSourceAndDelegate.setValueToCellConverters(valueToCellConverters) - self._tableView.setRowHeight_(max(rowHeights)) + else: + resizingMask = AppKit.NSTableColumnUserResizingMask | AppKit.NSTableColumnAutoresizingMask + column = AppKit.NSTableColumn.alloc().initWithIdentifier_(identifier) + column.setTitle_(title) + column.setResizingMask_(resizingMask) + if width is not None: + column.setWidth_(width) + column.setMinWidth_(minWidth) + column.setMaxWidth_(maxWidth) + if self._allowsSorting and sortable: + sortDescriptor = AppKit.NSSortDescriptor.sortDescriptorWithKey_ascending_selector_( + identifier, + True, + "compare:" + ) + column.setSortDescriptorPrototype_(sortDescriptor) + self._tableView.addTableColumn_(column) + + if index != -1 and index != len(self._tableView.tableColumns()): + self._tableView.moveColumn_toColumn_( + self._tableView.columnWithIdentifier_(identifier), + index + ) + # measure the cell to get the row height + cell = cellClass(**cellKwargs) + height = cell._nsObject.fittingSize().height + del cell + if height > self._tableView.rowHeight(): + self._tableView.setRowHeight_(height) def _wrapItem(self, item): if isinstance(item, simpleDataTypes): @@ -872,6 +953,18 @@ def setSelectedIndexes(self, indexes): rowIndexes = makeIndexSet(rowIndexes) self._tableView.selectRowIndexes_byExtendingSelection_(rowIndexes, False) + def getEditedIndex(self): + """ + Return the index of the edited row. + """ + return self._dataSourceAndDelegate.getEditedRowIndex() + + def getEditedItem(self): + """ + Return the item of the edited row. + """ + return self.get()[self.getEditedIndex()] + def scrollToSelection(self): """ Scroll the selected rows to visible. @@ -943,6 +1036,8 @@ def _keyDown(self, event): return True elif self._enableDelete: self.removeSelection() + if self._editCallback is not None: + self._editCallback(self) return True return False @@ -950,7 +1045,7 @@ def _keyDown(self, event): def _menuForEvent(self, event): # this method is called by the NSTableView subclass to request a contextual menu - # if there is a menuCallack convert a the incomming items to an nsmenu + # if there is a menuCallack convert the incoming items to an nsmenu if self._menuCallback is not None: items = self._menuCallback(self) # if the list is empty or None, dont do anything @@ -1028,6 +1123,7 @@ def makeIndexSet(indexes): from vanilla.vanillaGroup import Group from vanilla.vanillaEditText import EditText +from vanilla.vanillaTextBox import _textAlignmentMap truncationMap = dict( clipping=AppKit.NSLineBreakByClipping, @@ -1041,6 +1137,14 @@ class EditTextList2Cell(Group): """ An object that displays text in a List2 column. + **alignment** The alignment of the text within the row. Options: + + - `"left"` + - `"right"` + - `"center"` + - `"justified"` + - `"natural"` + **verticalAlignment** The vertical alignment of the text within the row. Options: @@ -1079,20 +1183,35 @@ class EditTextList2Cell(Group): # Sigh. def __init__(self, + alignment="natural", verticalAlignment="center", editable=False, truncationMode="tail", - callback=None + callback=None, + continuous=False ): self._externalCallback = callback super().__init__("auto") self.editText = EditText( "auto", readOnly=not editable, - callback=self._internalCallback + callback=self._internalCallback, + continuous=continuous ) container = self._nsObject textField = self.editText.getNSTextField() + # Hi! If you are here trying to debug a strange auto layout issue + # where the cell inside of a table is affecting the fixed position + # layout of controls outside of the table heirarchy, well, good news: + # I'm here to help, bad news: I have no clue what is going on. + # The initial version used method 1. Then this issue was reported: + # https://github.com/robotools/vanilla/issues/188 + # I created method 2 thinking that having tight control over the layout + # would work better than doing it through the text syntax. Nope. It + # was just a separate set of problems. So, future reader, I leave my + # code here for you to see so that you don't venture down the same + # pointless path. + # > method 1 if verticalAlignment == "top": yRule = "V:|[editText]" elif verticalAlignment == "bottom": @@ -1117,10 +1236,22 @@ def __init__(self, ) rules.append(heightRule) self.addAutoPosSizeRules(rules) + # < method 1 + # > method 2 + # textField.setTranslatesAutoresizingMaskIntoConstraints_(False) + # textField.widthAnchor().constraintEqualToAnchor_(container.widthAnchor()).setActive_(True) + # if verticalAlignment == "top": + # textField.topAnchor().constraintEqualToAnchor_(container.topAnchor()).setActive_(True) + # elif verticalAlignment == "bottom": + # textField.bottomAnchor().constraintEqualToAnchor_(container.bottomAnchor()).setActive_(True) + # elif verticalAlignment == "center": + # textField.centerYAnchor().constraintEqualToAnchor_(container.centerYAnchor()).setActive_(True) + # < method 2 textField.setDrawsBackground_(False) textField.setBezeled_(False) lineBreakMode = truncationMap.get(truncationMode, truncationMode) textField.setLineBreakMode_(lineBreakMode) + textField.setAlignment_(_textAlignmentMap[alignment]) def getNSTextField(self): return self.editText.getNSTextField() @@ -1276,11 +1407,13 @@ class PopUpButtonList2Cell(PopUpButton): def __init__(self, items=[], editable=False, - callback=None + callback=None, + bordered=False, ): super().__init__( "auto", items=items, + bordered=bordered, sizeStyle="small", callback=callback ) @@ -1487,3 +1620,34 @@ def __init__(self, image = imageObject if imageObject is not None: cell.setImage_(image) + + +from vanilla.vanillaComboBox import ComboBox + +class ComboBoxList2Cell(ComboBox): + + """ + An object that displays a combo box in a List2 column. + Refer to the ComboBox documentation for options. + + .. note:: + This class should only be used in the *columnDescriptions* + *cellClass* argument during the construction of a List. + This is never constructed directly. + """ + + def __init__(self, + items=[], + completes=True, + editable=False, + callback=None + ): + super().__init__( + posSize="auto", + items=items, + completes=completes, + continuous=False, + sizeStyle="small", + callback=callback + ) + self.enable(editable) diff --git a/Lib/vanilla/vanillaPopUpButton.py b/Lib/vanilla/vanillaPopUpButton.py index 66f0db3..890e9f1 100644 --- a/Lib/vanilla/vanillaPopUpButton.py +++ b/Lib/vanilla/vanillaPopUpButton.py @@ -68,7 +68,7 @@ def popUpButtonCallback(self, sender): "regular": (-3, -4, 6, 6), } - def __init__(self, posSize, items, callback=None, sizeStyle="regular"): + def __init__(self, posSize, items, callback=None, sizeStyle="regular", bordered=True): self._setupView(self.nsPopUpButtonClass, posSize) if self.nsPopUpButtonCellClass != NSPopUpButtonCell: self._nsObject.setCell_(self.nsPopUpButtonCellClass.alloc().init()) @@ -76,6 +76,7 @@ def __init__(self, posSize, items, callback=None, sizeStyle="regular"): self._setCallback(callback) self._setSizeStyle(sizeStyle) self.setItems(items) + self._nsObject.setBordered_(bordered) def getNSPopUpButton(self): """ @@ -114,10 +115,14 @@ def setItems(self, items): Set the items to appear in the pop up list. """ self._nsObject.removeAllItems() + menu = self._nsObject.menu() for item in items: if isinstance(item, NSMenuItem): - menu = self._nsObject.menu() menu.addItem_(item) + elif item == "---": + separatorItem = NSMenuItem.separatorItem() + separatorItem.setTitle_(item) + menu.addItem_(separatorItem) else: self._nsObject.addItemWithTitle_(item) @@ -208,10 +213,9 @@ def subFirstCallback(self, sender): """ def __init__(self, posSize, items, sizeStyle="regular", bordered=True): - super().__init__(posSize, items, sizeStyle=sizeStyle) + super().__init__(posSize, items, sizeStyle=sizeStyle, bordered=bordered) self._nsObject.setPullsDown_(True) self._nsObject.setBezelStyle_(NSTexturedRoundedBezelStyle) - self._nsObject.setBordered_(bordered) def _breakCycles(self): self._menuItemCallbackWrappers = None diff --git a/Lib/vanilla/vanillaRadioGroup.py b/Lib/vanilla/vanillaRadioGroup.py index 8c4fcf8..3ecc328 100644 --- a/Lib/vanilla/vanillaRadioGroup.py +++ b/Lib/vanilla/vanillaRadioGroup.py @@ -2,6 +2,7 @@ from vanilla.vanillaBase import VanillaBaseControl, _sizeStyleMap from vanilla.vanillaButton import Button from vanilla.vanillaStackGroup import VerticalStackGroup, HorizontalStackGroup +from objc import super try: from AppKit import NSRadioButtonType @@ -23,7 +24,7 @@ def _init(self, cls, posSize, titles, callback=None, sizeStyle="regular"): self._buttonHeight = self._heights[sizeStyle] self._callback = callback self._sizeStyle = sizeStyle - super().__init__(posSize, spacing=spacing, alignment="leading") + cls.__init__(self, posSize, spacing=spacing, alignment="leading") self._buildButtons(titles, sizeStyle) def _buildButtons(self, titles, sizeStyle): @@ -66,6 +67,13 @@ def set(self, index): for other, button in enumerate(self._buttons): button.set(other == index) + def enable(self, onOff): + """ + Enable or disable the object. **onOff** should be a boolean. + """ + for button in self._buttons: + button.enable(onOff) + class VerticalRadioGroup(VerticalStackGroup, _RadioGroupMixin): @@ -126,7 +134,7 @@ def radioGroupCallback(self, sender): ) def __init__(self, posSize, titles, callback=None, sizeStyle="regular"): - self._init(VerticalRadioGroup, posSize, titles, callback=callback, sizeStyle=sizeStyle) + self._init(VerticalStackGroup, posSize, titles, callback=callback, sizeStyle=sizeStyle) class HorizontalRadioGroup(HorizontalStackGroup, _RadioGroupMixin): @@ -188,7 +196,7 @@ def radioGroupCallback(self, sender): ) def __init__(self, posSize, titles, callback=None, sizeStyle="regular"): - self._init(HorizontalRadioGroup, posSize, titles, callback=callback, sizeStyle=sizeStyle) + self._init(HorizontalStackGroup, posSize, titles, callback=callback, sizeStyle=sizeStyle) class RadioButton(Button): diff --git a/Lib/vanilla/vanillaWindows.py b/Lib/vanilla/vanillaWindows.py index 805e7a9..56bcaf2 100644 --- a/Lib/vanilla/vanillaWindows.py +++ b/Lib/vanilla/vanillaWindows.py @@ -318,10 +318,10 @@ def _validateMinMaxSize(self): maxSize = self._window.maxSize() if size.width < minSize.width or size.height < minSize.height: from warnings import warn - warn("The windows `minSize` is bigger then the initial size.", VanillaWarning) + warn("The window's `minSize` is bigger than the initial size.", VanillaWarning) elif size.width > maxSize.width or size.height > maxSize.height: from warnings import warn - warn("The windows `maxSize` is bigger then the initial size.", VanillaWarning) + warn("The window's initial size is bigger than the `maxSize`.", VanillaWarning) def close(self): """ @@ -1232,4 +1232,4 @@ def windowWillClose_(self, notification): # XXX hack to get around vanilla derivatives that # have already implemented a class named ModalWindow -ModalWindow = VModalWindow \ No newline at end of file +ModalWindow = VModalWindow