CreativePython 1.1.4__py3-none-any.whl → 1.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,11 +86,20 @@ def _launchRenderer(childCommandConnection, childPriorityConnection, parentPrior
86
86
 
87
87
  parentPriorityConnection.close() # child must not hold the parent end open
88
88
 
89
- # Redirect stderr (fd 2) to a log file so both Python tracebacks and C-level
90
- # output from Qt/Cocoa/XPC are captured for diagnostics.
91
- # The log is removed on clean exit if nothing was written to it.
92
- _logPath = os.path.join(os.getcwd(), "PENCIL_debug.log")
93
- _logFile = open(_logPath, "w")
89
+ # Set to True to redirect stderr to PENCIL_debug.log for diagnostics.
90
+ # When False, stderr is silenced (sent to /dev/null) to suppress Qt/Cocoa noise.
91
+ _DEBUG_LOG = False
92
+
93
+ if _DEBUG_LOG:
94
+ # Redirect stderr (fd 2) to a log file so both Python tracebacks and C-level
95
+ # output from Qt/Cocoa/XPC are captured for diagnostics.
96
+ # The log is removed on clean exit if nothing was written to it.
97
+ _logPath = os.path.join(os.getcwd(), "PENCIL_debug.log")
98
+ _logFile = open(_logPath, "w")
99
+ else:
100
+ _logPath = None
101
+ _logFile = open(os.devnull, "w")
102
+
94
103
  os.dup2(_logFile.fileno(), 2)
95
104
  sys.stderr = _logFile
96
105
 
@@ -103,7 +112,7 @@ def _launchRenderer(childCommandConnection, childPriorityConnection, parentPrior
103
112
  raise
104
113
  finally:
105
114
  _logFile.flush()
106
- if os.path.getsize(_logPath) == 0:
115
+ if _DEBUG_LOG and _logPath and os.path.getsize(_logPath) == 0:
107
116
  try:
108
117
  _logFile.close()
109
118
  os.unlink(_logPath)
@@ -549,9 +549,10 @@ class _QtGraphicsItemEventMixin:
549
549
  pos = event.scenePos()
550
550
  args = [int(pos.x()), int(pos.y())]
551
551
  self._send('mouseDown', args)
552
- # register this item for drag delivery during subsequent mouse moves
552
+ # register this item for release and drag delivery during subsequent mouse events
553
553
  view = self._qtview()
554
554
  if view is not None:
555
+ view._pressedItems.append(self._mirror.objectId) # track for manual release delivery (see QtView.mouseReleaseEvent)
555
556
  if (self._mirror.objectId, 'mouseDrag') in self._mirror.guiRenderer._registeredEvents:
556
557
  view._draggingItems.append(self._mirror.objectId)
557
558
  event.ignore() # allow cascade to items below
@@ -623,7 +624,30 @@ class _QPolygonItem(_QtGraphicsItemEventMixin, QtWidgets.QGraphicsPolygonItem):
623
624
  pass
624
625
 
625
626
  class _QPixmapItem(_QtGraphicsItemEventMixin, QtWidgets.QGraphicsPixmapItem):
626
- pass
627
+ _colorFilter = None # (r, g, b, a) set by IconMirror._applyColor; None = no filter
628
+
629
+ def paint(self, painter, option, widget=None):
630
+ if self._colorFilter is None:
631
+ super().paint(painter, option, widget)
632
+ return
633
+
634
+ pixmap = self.pixmap()
635
+ if pixmap.isNull():
636
+ return
637
+
638
+ r, g, b, a = self._colorFilter
639
+ image = pixmap.toImage().convertToFormat(QtGui.QImage.Format.Format_ARGB32)
640
+
641
+ filtered = QtGui.QImage(image.size(), QtGui.QImage.Format.Format_ARGB32)
642
+ filtered.fill(QtCore.Qt.GlobalColor.transparent)
643
+
644
+ p = QtGui.QPainter(filtered)
645
+ p.drawImage(0, 0, image)
646
+ p.setCompositionMode(QtGui.QPainter.CompositionMode.CompositionMode_SourceOver)
647
+ p.fillRect(image.rect(), QtGui.QColor(r, g, b, a))
648
+ p.end()
649
+
650
+ painter.drawImage(self.offset(), filtered)
627
651
 
628
652
  class _QGroupItem(_QtGraphicsItemEventMixin, QtWidgets.QGraphicsItemGroup):
629
653
  pass
@@ -653,6 +677,7 @@ class QtView(QtWidgets.QGraphicsView):
653
677
  self._display = displayMirror
654
678
  self._pressPos = None # QPointF scene pos at last press
655
679
  self._draggingItems = [] # objectIds wanting drag events
680
+ self._pressedItems = [] # objectIds of items that received the last mousePressEvent
656
681
 
657
682
  # ── Helpers ────────────────────────────────────────────────────────────────
658
683
 
@@ -679,16 +704,28 @@ class QtView(QtWidgets.QGraphicsView):
679
704
 
680
705
  def mouseReleaseEvent(self, event):
681
706
  x, y = self._sceneXY(event)
682
- super().mouseReleaseEvent(event) # → item cascade via mixin
707
+
708
+ # Items call event.ignore() in mousePressEvent to allow cascade, so Qt never
709
+ # sets a mouse grabber and mouseReleaseEvent is never routed to items normally.
710
+ # Manually deliver mouseUp and mouseClick using the objectIds tracked at press time,
711
+ # mirroring the _draggingItems pattern used for mouseDrag.
712
+ registered = self._display.guiRenderer._registeredEvents
713
+ pos = self.mapToScene(event.position().toPoint())
714
+ is_click = (self._pressPos is not None
715
+ and abs(pos.x() - self._pressPos.x()) <= _QtGraphicsItemEventMixin._CLICK_THRESHOLD
716
+ and abs(pos.y() - self._pressPos.y()) <= _QtGraphicsItemEventMixin._CLICK_THRESHOLD)
717
+ for objectId in self._pressedItems:
718
+ if (objectId, 'mouseUp') in registered:
719
+ self._display.guiRenderer.sendEvent('mouseUp', objectId, [x, y])
720
+ if is_click and (objectId, 'mouseClick') in registered:
721
+ self._display.guiRenderer.sendEvent('mouseClick', objectId, [x, y])
722
+
683
723
  self._sendDisplay('mouseUp', [x, y])
684
- if self._pressPos is not None:
685
- pos = self.mapToScene(event.position().toPoint())
686
- dx = abs(pos.x() - self._pressPos.x())
687
- dy = abs(pos.y() - self._pressPos.y())
688
- if dx <= _QtGraphicsItemEventMixin._CLICK_THRESHOLD and dy <= _QtGraphicsItemEventMixin._CLICK_THRESHOLD:
689
- self._sendDisplay('mouseClick', [x, y])
724
+ if is_click:
725
+ self._sendDisplay('mouseClick', [x, y])
690
726
  self._pressPos = None
691
727
  self._draggingItems = []
728
+ self._pressedItems = []
692
729
 
693
730
  def mouseMoveEvent(self, event):
694
731
  x, y = self._sceneXY(event)
@@ -1674,15 +1711,12 @@ class _DrawableMirror:
1674
1711
  self._rotation = 0
1675
1712
  self._toolTipText = None
1676
1713
 
1677
- # GuiRenderer only handles commands that alter an object visually,
1678
- # or that rely on hit testing. Everything else is handled in gui.py
1714
+ # GuiRenderer only handles commands that alter an object visually.
1715
+ # Hit testing (contains/intersects/encloses) is handled entirely in gui.py.
1679
1716
  self._commandHandlers = {
1680
1717
  'setPosition': self._setPosition,
1681
1718
  'setSize': self._setSize,
1682
1719
  'setRotation': self._setRotation,
1683
- 'contains': self._contains,
1684
- 'intersects': self._intersects,
1685
- 'encloses': self._encloses,
1686
1720
  'setToolTipText': self._setToolTipText,
1687
1721
  'show': self._show,
1688
1722
  'hide': self._hide,
@@ -1745,43 +1779,6 @@ class _DrawableMirror:
1745
1779
  self.qObject.prepareGeometryChange()
1746
1780
  self.qObject.setRotation(qtDegree)
1747
1781
 
1748
- # ── Hit testing ────────────────────────────────────────────────────────────
1749
-
1750
- def _contains(self, args, responseId):
1751
- """Returns True if the point (x, y) is inside this item."""
1752
- x = args.get('x', 0)
1753
- y = args.get('y', 0)
1754
- globalPoint = QtCore.QPointF(x, y)
1755
- localPoint = self.qObject.mapFromScene(globalPoint)
1756
- result = self.qObject.contains(localPoint)
1757
- self.guiRenderer.sendResponse(responseId, [result])
1758
-
1759
- def _intersects(self, args, responseId):
1760
- """Returns True if this item intersects another item."""
1761
- otherObjectId = args.get('otherObjectId')
1762
- otherMirror = self.guiRenderer._objectRegistry.get(otherObjectId)
1763
- result = False
1764
-
1765
- if otherMirror is not None:
1766
- pathA = self.qObject.mapToScene(self.qObject.shape())
1767
- pathB = otherMirror.qObject.mapToScene(otherMirror.qObject.shape())
1768
- result = pathA.intersects(pathB)
1769
-
1770
- self.guiRenderer.sendResponse(responseId, [result])
1771
-
1772
- def _encloses(self, args, responseId):
1773
- """Returns True if this item fully encloses another item."""
1774
- otherObjectId = args.get('otherObjectId')
1775
- otherMirror = self.guiRenderer._objectRegistry.get(otherObjectId)
1776
- result = False
1777
-
1778
- if otherMirror is not None:
1779
- pathA = self.qObject.mapToScene(self.qObject.shape())
1780
- pathB = otherMirror.qObject.mapToScene(otherMirror.qObject.shape())
1781
- result = pathA.contains(pathB)
1782
-
1783
- self.guiRenderer.sendResponse(responseId, [result])
1784
-
1785
1782
  # ── Visibility ─────────────────────────────────────────────────────────────
1786
1783
 
1787
1784
  def _show(self, args, responseId):
@@ -2391,6 +2388,7 @@ class PolygonMirror(_GraphicsMirror):
2391
2388
  # IconMirror — mirror of gui.py's Icon
2392
2389
  #######################################################################################
2393
2390
 
2391
+
2394
2392
  class IconMirror(_GraphicsMirror):
2395
2393
  """
2396
2394
  Mirror of gui.py's Icon. Backed by a QGraphicsPixmapItem.
@@ -2460,16 +2458,25 @@ class IconMirror(_GraphicsMirror):
2460
2458
  'getPixels': self._getPixels,
2461
2459
  'setPixels': self._setPixels,
2462
2460
  'write': self._write,
2461
+ 'setAlpha': self._setAlpha,
2463
2462
  })
2464
2463
 
2465
2464
  def _applyColor(self):
2466
- """No-op QGraphicsPixmapItem has no pen or brush."""
2467
- pass
2465
+ r, g, b, a = self._color
2466
+ if [r, g, b, a] == [0, 0, 0, 0]:
2467
+ self.qObject._colorFilter = None
2468
+ else:
2469
+ self.qObject._colorFilter = (r, g, b, a)
2470
+ self.qObject.update()
2468
2471
 
2469
2472
  def _applyThickness(self):
2470
2473
  """No-op — QGraphicsPixmapItem has no pen or brush."""
2471
2474
  pass
2472
2475
 
2476
+ def _setAlpha(self, args, responseId):
2477
+ alpha = args.get('alpha', 255)
2478
+ self.qObject.setOpacity(alpha / 255.0)
2479
+
2473
2480
  # ── Size ───────────────────────────────────────────────────────────────────
2474
2481
 
2475
2482
  def _getSize(self, args, responseId):
@@ -2908,8 +2915,7 @@ class _ControlMirror(_DrawableMirror):
2908
2915
  - dimensions: setRect(), setPath(), etc. -> setFixedSize()
2909
2916
  - color: [r, g, b] -> stylesheets
2910
2917
 
2911
- Controls have no hit-testing (contains/intersects/encloses) because QWidgets
2912
- handle their own input directly.
2918
+ Controls handle their own input directly via Qt's widget event system.
2913
2919
 
2914
2920
  Concrete classes must:
2915
2921
  1. Call super().__init__(objectId, guiRenderer, args) first.
@@ -2950,48 +2956,6 @@ class _ControlMirror(_DrawableMirror):
2950
2956
  def _setRotation(self, args, responseId):
2951
2957
  pass # Controls cannot be rotated; gui.py warns at the call site
2952
2958
 
2953
- # ── Hit testing ────────────────────────────────────────────────────────────
2954
-
2955
- def _contains(self, args, responseId):
2956
- """Returns True if the point (x, y) is inside this widget's bounding box."""
2957
- x = args.get('x', 0)
2958
- y = args.get('y', 0)
2959
- rect = QtCore.QRectF(self.qObject.geometry())
2960
- result = rect.contains(QtCore.QPointF(x, y))
2961
- self.guiRenderer.sendResponse(responseId, [result])
2962
-
2963
- def _intersects(self, args, responseId):
2964
- """Returns True if this widget's bounding box intersects another item."""
2965
- otherObjectId = args.get('otherObjectId')
2966
- otherMirror = self.guiRenderer._objectRegistry.get(otherObjectId)
2967
- result = False
2968
-
2969
- if otherMirror is not None:
2970
- rectA = QtCore.QRectF(self.qObject.geometry())
2971
- if isinstance(otherMirror, _ControlMirror):
2972
- rectB = QtCore.QRectF(otherMirror.qObject.geometry())
2973
- else:
2974
- rectB = otherMirror.qObject.sceneBoundingRect()
2975
- result = rectA.intersects(rectB)
2976
-
2977
- self.guiRenderer.sendResponse(responseId, [result])
2978
-
2979
- def _encloses(self, args, responseId):
2980
- """Returns True if this widget's bounding box fully encloses another item."""
2981
- otherObjectId = args.get('otherObjectId')
2982
- otherMirror = self.guiRenderer._objectRegistry.get(otherObjectId)
2983
- result = False
2984
-
2985
- if otherMirror is not None:
2986
- rectA = QtCore.QRectF(self.qObject.geometry())
2987
- if isinstance(otherMirror, _ControlMirror):
2988
- rectB = QtCore.QRectF(otherMirror.qObject.geometry())
2989
- else:
2990
- rectB = otherMirror.qObject.sceneBoundingRect()
2991
- result = rectA.contains(rectB)
2992
-
2993
- self.guiRenderer.sendResponse(responseId, [result])
2994
-
2995
2959
  # ── Size ───────────────────────────────────────────────────────────────────
2996
2960
 
2997
2961
  def _getSize(self, args, responseId):
@@ -1,4 +1,10 @@
1
1
  import platform, ctypes, importlib.resources
2
+ from importlib.metadata import version as _metadata_version
3
+
4
+ try:
5
+ __version__ = _metadata_version("CreativePython")
6
+ except Exception:
7
+ __version__ = "unknown"
2
8
 
3
9
 
4
10
  # import portaudio binary on MacOS
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CreativePython
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: A Python-based software environment for developing algorithmic art projects.
5
5
  Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
6
6
  License: MIT License
@@ -1,16 +1,16 @@
1
- gui.py,sha256=2UjM25KRq8NdA6D5mUeb797tPBklTbUwBLf7uKm7W5A,134968
1
+ gui.py,sha256=io7p7aYK9EqcP9lBbBO3CBycaAvaHs3IxNw5BrOhhgs,154434
2
2
  iannix.py,sha256=jlBJhPzBcIl2iayG01ldoslAytxYOvP3z_vN-EgiJA4,17188
3
- image.py,sha256=-jp7ewEcw60GdzqGfcRMXons1nTEEuml6yZmTeWkc1w,3703
3
+ image.py,sha256=wohpTTZH6QzN3wRfXI5ac7VTRsOKcwqk8_d3yha8oDw,3763
4
4
  markov.py,sha256=VhTXZfDDHETj6KhriFmOLHKNjgx08cWZvpLECuGRTG0,24653
5
- midi.py,sha256=IUS5grFuI23cun7R5wa5AyBZDGhFJsxzqXPrfaN4cEs,42826
6
- music.py,sha256=--h3xSVTFJL59sjSN6vLho9Y-XCktcSOxgVAYHu1bZo,266610
7
- osc.py,sha256=76qLrFE_vXLaqi0c4XDilromvVSZjPMseNIpQm9C1IM,18724
5
+ midi.py,sha256=avVY6dyTHbFazt10w2Uy0zUPXvRYleZM2O89gDX3-h4,27203
6
+ music.py,sha256=VyYcjxEIRYG7YWFzBIbSFaR4mVcO2fHDZnkdCh0G4sM,268532
7
+ osc.py,sha256=GtjWV5LD4xiW9Yen3PzFYstWHhEZ_QyrKunAu-VsPrc,8353
8
8
  timer.py,sha256=uQ1BHWTjZ6DpJo-leGhxgOJcVcd1Lj0jvQQodXx1kw4,16341
9
9
  zipf.py,sha256=J6fWwUYz88LFAwanc0TmXJlU7VtpBM24_Rw5XJTOn9c,40312
10
- CreativePython/GuiHandler.py,sha256=sNwvHlcHJhq61l5r8hXKeDh_17RG_ewI-aZ9rx49UjU,30662
11
- CreativePython/GuiRenderer.py,sha256=CpjBcc_NnZ4UW1HtIRy_PjIXapZ_thKpGI_RG4mX0AQ,138519
10
+ CreativePython/GuiHandler.py,sha256=ONnxhqivBrzCHQv7GcE4vkMgnONbuXXq7Fwe0Au5QPs,30974
11
+ CreativePython/GuiRenderer.py,sha256=XUXhPjtLRUA3D0Q05ZP85gb3gPqg8Zvs732bSYBOVo4,136843
12
12
  CreativePython/RealtimeAudioPlayer.py,sha256=tjqBjSOvSvH1kP0t6q35xsbKHPr0wuLUvGdxdWAY73E,44590
13
- CreativePython/__init__.py,sha256=lQcg42OKznQmgyt_WYsrsXFen2sJK4j0TUBGtCX-54Y,285
13
+ CreativePython/__init__.py,sha256=bT1nZ-Fp8-7uiKqhVdnFeOwL-8Jps2rvE6BfoOE8fZ0,451
14
14
  CreativePython/notationRenderer.py,sha256=2KIMvInNLEUZyn_r1KMmyt7X2XDrfekVkggFeHLRu1M,35440
15
15
  CreativePython/nevmuse/RunMetrics.py,sha256=wP5HeS7u5VmH9s-9yyF1EuvF8xFcU6oYTT90Qr16Mu0,4917
16
16
  CreativePython/nevmuse/Surveyor.py,sha256=y5_GgTpfZl4IteymBD1-I1lduZuw-ap1mS9zgnOamUc,4794
@@ -77,9 +77,9 @@ CreativePython/nevmuse/utilities/CSVWriter.py,sha256=k2GccvzD5IwdkmFdN5qtRA2qZX1
77
77
  CreativePython/nevmuse/utilities/PowerLawRandom.py,sha256=WIEI-p8wiiWhkkBR5pO9gJ2TwxZBw453T_xXzCNdKPY,3752
78
78
  CreativePython/nevmuse/utilities/__init__.py,sha256=pAGIMMGfLvR2DQfxr8qkfKQJ-4fIkVqlhnGK657JPL8,166
79
79
  bin/libportaudio.2.dylib,sha256=ooJlm7QeL7jYzc1c8YMmH-C8PhYHwDvBJZkNDVPFRbE,286592
80
- creativepython-1.1.4.dist-info/licenses/LICENSE,sha256=tz38qDa5RhHMqzZR1Q5QafqWdXn_EhheXYBP7FjgtEM,1992
81
- creativepython-1.1.4.dist-info/licenses/LICENSE-PSF,sha256=JN1jUPIsEUH1mq3AXxet-b1IdhAr-XzscXBZiz74c9c,3371
82
- creativepython-1.1.4.dist-info/METADATA,sha256=qM6dP7eB2aczJ8OJxuKcfVU5EpfKSpbeoBpOqiTe6Hw,8703
83
- creativepython-1.1.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
84
- creativepython-1.1.4.dist-info/top_level.txt,sha256=PzvvUyy-3YzTedVGnLS02ju1TCMuo76h1LuVd8XvHhA,69
85
- creativepython-1.1.4.dist-info/RECORD,,
80
+ creativepython-1.1.6.dist-info/licenses/LICENSE,sha256=tz38qDa5RhHMqzZR1Q5QafqWdXn_EhheXYBP7FjgtEM,1992
81
+ creativepython-1.1.6.dist-info/licenses/LICENSE-PSF,sha256=JN1jUPIsEUH1mq3AXxet-b1IdhAr-XzscXBZiz74c9c,3371
82
+ creativepython-1.1.6.dist-info/METADATA,sha256=MGKp9oy0r3G1TsnJxa2M9WtRlP_4X7bGv-05HyB2z0s,8703
83
+ creativepython-1.1.6.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
84
+ creativepython-1.1.6.dist-info/top_level.txt,sha256=PzvvUyy-3YzTedVGnLS02ju1TCMuo76h1LuVd8XvHhA,69
85
+ creativepython-1.1.6.dist-info/RECORD,,