From 2ad32c34d6a7a699a2687cd402fbb994093e5afc Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 13:13:53 -0500 Subject: [PATCH 01/21] Gride hack. This closes #158. --- Lib/vanilla/vanillaGridView.py | 6 ++++++ 1 file changed, 6 insertions(+) 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): From dca980fb8ec3a7f0c8147cc0d2bde03de187ee13 Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 13:21:56 -0500 Subject: [PATCH 02/21] Add alignment option. This closes #161. --- Lib/vanilla/vanillaList2.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index d61385f..aa7738d 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -1028,6 +1028,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 +1042,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,6 +1088,7 @@ class EditTextList2Cell(Group): # Sigh. def __init__(self, + alignment="natural", verticalAlignment="center", editable=False, truncationMode="tail", @@ -1121,6 +1131,7 @@ def __init__(self, textField.setBezeled_(False) lineBreakMode = truncationMap.get(truncationMode, truncationMode) textField.setLineBreakMode_(lineBreakMode) + textField.setAlignment_(_textAlignmentMap[alignment]) def getNSTextField(self): return self.editText.getNSTextField() From 06de88b929a6252b4010da20596957e6304ce728 Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 13:32:46 -0500 Subject: [PATCH 03/21] Handle wrapper being None. --- Lib/vanilla/vanillaList2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index aa7738d..7591cfe 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -295,7 +295,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) From d3e0365134f9afedfc7ec3f09cbf7d994f4621d6 Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 16:37:17 -0500 Subject: [PATCH 04/21] Tried to fix #188. Failed. Left some notes. --- Lib/vanilla/vanillaList2.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 7591cfe..a2fadeb 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -1107,6 +1107,18 @@ def __init__(self, ) 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": @@ -1131,6 +1143,17 @@ 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) From d4c0085a37987f63934d775abbc7c0ac660edc4e Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 16:41:07 -0500 Subject: [PATCH 05/21] Call the edit callback after a delete. --- Lib/vanilla/vanillaList2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index a2fadeb..4b8fd4e 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -947,6 +947,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 From 4ee869d3196207119d8d1b243cd33ce900f6d36a Mon Sep 17 00:00:00 2001 From: Tal Leming Date: Wed, 20 Dec 2023 17:34:22 -0500 Subject: [PATCH 06/21] Add ComboBoxList2Cell. This closes #68. --- Lib/vanilla/__init__.py | 17 +++++++++++++++-- Lib/vanilla/test/list2/cells.py | 22 ++++++++++++++++++++++ Lib/vanilla/vanillaList2.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 3 deletions(-) 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/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 4b8fd4e..9bd270b 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -956,7 +956,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 @@ -1527,3 +1527,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) From 8c8403bdf758c5806efbac85f1d0bdc38076ff0a Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Tue, 23 Jan 2024 17:02:19 +0100 Subject: [PATCH 07/21] Add support for separators in a PopUpButton --- Lib/vanilla/vanillaPopUpButton.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/vanilla/vanillaPopUpButton.py b/Lib/vanilla/vanillaPopUpButton.py index 66f0db3..056f9c1 100644 --- a/Lib/vanilla/vanillaPopUpButton.py +++ b/Lib/vanilla/vanillaPopUpButton.py @@ -114,10 +114,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) From 8ee74f2b8d0a021af8d4aa27a619e855d0fa0914 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 12 Feb 2024 11:07:55 +0100 Subject: [PATCH 08/21] add support for bordered in a PopUpButton and a list2 PopUpButton --- Lib/vanilla/vanillaList2.py | 4 +++- Lib/vanilla/vanillaPopUpButton.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 9bd270b..7597805 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -1316,11 +1316,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 ) diff --git a/Lib/vanilla/vanillaPopUpButton.py b/Lib/vanilla/vanillaPopUpButton.py index 056f9c1..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): """ @@ -212,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 From 83c8c5383c40abe9ae66ae70ca8496c0b75ee98e Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 19 Feb 2024 15:50:53 +0100 Subject: [PATCH 09/21] using super from objc --- Lib/vanilla/vanillaColorWell.py | 1 + 1 file changed, 1 insertion(+) 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: From 27b194300c46a2523c91b6ae1443fd65578f49f8 Mon Sep 17 00:00:00 2001 From: Just van Rossum Date: Fri, 22 Mar 2024 09:13:19 +0100 Subject: [PATCH 10/21] Fix superclass __init__ invokation --- Lib/vanilla/vanillaRadioGroup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/vanilla/vanillaRadioGroup.py b/Lib/vanilla/vanillaRadioGroup.py index 8c4fcf8..a13ef46 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): @@ -126,7 +127,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 +189,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): From b9a88e1ecb2d72209034a437255989e35098e86c Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Thu, 11 Apr 2024 14:51:00 +0200 Subject: [PATCH 11/21] Add support for enable in a radio StackGroup fixing #208 --- Lib/vanilla/vanillaRadioGroup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/vanilla/vanillaRadioGroup.py b/Lib/vanilla/vanillaRadioGroup.py index a13ef46..3ecc328 100644 --- a/Lib/vanilla/vanillaRadioGroup.py +++ b/Lib/vanilla/vanillaRadioGroup.py @@ -67,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): From 3dad8f29e0f2e41641e75009962edbcbfe543846 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Wed, 8 May 2024 12:00:11 +0200 Subject: [PATCH 12/21] Add support to retrieve which cell is edited this adds getEditedIndex() and getEditedItem() --- Lib/vanilla/vanillaList2.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 7597805..876e1da 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) } @@ -246,8 +247,14 @@ def cellEditCallback(self, sender): value = sender.get() self.setItemValueForColumnAndRow(value, identifier, row) 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 @@ -876,6 +883,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. From 7ec2205aa46b70f58a187ac4a313f75de6256b21 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Wed, 8 May 2024 14:10:30 +0200 Subject: [PATCH 13/21] improve sphinx docs (#209) fixing #207 fixing #204 --- Documentation/source/conf.py | 15 +- Documentation/source/index.rst | 1 + .../source/objects/FloatingWindow.rst | 188 ------------------ Documentation/source/objects/List.rst | 27 ++- Documentation/source/objects/List2.rst | 27 +-- Documentation/source/objects/ModalWindow.rst | 7 + Documentation/source/objects/Window.rst | 188 ------------------ 7 files changed, 49 insertions(+), 404 deletions(-) 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. From 7101624e0178617abbc30c1e75688a49342da0f4 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 24 Jun 2024 10:04:29 +0200 Subject: [PATCH 14/21] fixing deprecated inspect methods see https://github.com/ronaldoussoren/pyobjc/issues/610#issuecomment-2155856481 --- Lib/vanilla/vanillaBrowser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", "") From 9a9a2f4650b2183128d110a4a9de3a4a276efc8d Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Fri, 2 Aug 2024 17:21:31 +0200 Subject: [PATCH 15/21] check if the comboBox uses a dataSource to retrieve and call the changed callback --- Lib/vanilla/vanillaComboBox.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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) From d3313b735bd2007d47b3e378695c2d25d5f6ef17 Mon Sep 17 00:00:00 2001 From: "Colin M. Ford" Date: Wed, 28 Aug 2024 19:49:44 -0400 Subject: [PATCH 16/21] Correcting error messages in `_validateMinMaxSize` --- Lib/vanilla/vanillaWindows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 3ef4eba6dca0a94d14eef6da343524fd601f387d Mon Sep 17 00:00:00 2001 From: Ben Kiel Date: Thu, 29 Aug 2024 15:48:48 -0500 Subject: [PATCH 17/21] Update publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: | From fd9082bf71b289dddd550cd7423e1e411b344405 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Sat, 14 Sep 2024 22:53:57 +0200 Subject: [PATCH 18/21] Add support for List2 appendColumn and removeColumn --- Lib/vanilla/test/list2/columns.py | 96 ++++++++++++++ Lib/vanilla/test/testAll.py | 2 +- Lib/vanilla/vanillaList2.py | 207 +++++++++++++++++++----------- 3 files changed, 231 insertions(+), 74 deletions(-) create mode 100644 Lib/vanilla/test/list2/columns.py 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/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 876e1da..47214b2 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -57,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): @@ -157,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 @@ -638,84 +658,125 @@ 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 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): From 709d9ef79e3a26cb274ec86675183bfa90c4ea55 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 30 Sep 2024 17:02:03 +0200 Subject: [PATCH 19/21] Set EditTextList2Cell continuous to False by default --- Lib/vanilla/vanillaList2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 47214b2..dd95684 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -1178,14 +1178,16 @@ def __init__(self, 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() From d36d787cf3a0259b737e7622e684bb0298aab2df Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 30 Sep 2024 17:02:32 +0200 Subject: [PATCH 20/21] Call cell converter after edit and set the value back into the sender cell --- Lib/vanilla/vanillaList2.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index dd95684..8aa2db3 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -199,6 +199,7 @@ def setItemValueForColumnAndRow(self, value, identifier, row): return function(item, value) elif isinstance(item, dict): item[identifier] = value + return value # Data Source @@ -265,7 +266,9 @@ 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: From 2d82b350875eae3272edf99c890f1d9804a9eb63 Mon Sep 17 00:00:00 2001 From: Frederik Berlaen Date: Mon, 30 Sep 2024 22:25:41 +0200 Subject: [PATCH 21/21] Add support for List2. getColumnIdentifiers --- Lib/vanilla/vanillaList2.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/vanilla/vanillaList2.py b/Lib/vanilla/vanillaList2.py index 47214b2..56e7d44 100644 --- a/Lib/vanilla/vanillaList2.py +++ b/Lib/vanilla/vanillaList2.py @@ -665,6 +665,12 @@ def _buildColumns(self, columnDescriptions): for columnDescription in columnDescriptions: 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.