Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Checks: >
-bugprone-throwing-static-initialization,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-optin.performance.Padding,
-clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker
# Only report findings in matplotlib's own src/ headers, not in pybind11,
# Python.h, agg, or other vendored includes.
Expand Down
4 changes: 2 additions & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"GUI: MacOSX":
- changed-files:
- any-glob-to-any-file:
- 'lib/matplotlib/backends/*_macosx.py*'
- 'src/_macosx.m'
- 'lib/matplotlib/backends/backend_legacymac.py'
- 'src/_legacymac.m'
"GUI: nbagg":
- changed-files:
- any-glob-to-any-file:
Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,8 +1230,9 @@ def use(backend, *, force=True):
backend names, which are case-insensitive:
- interactive backends:
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, notebook, QtAgg,
QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, LegacyMac, macOS, MacOSX,
nbAgg, notebook, QtAgg, QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg,
WXCairo, Qt5Agg, Qt5Cairo
- non-interactive backends:
agg, cairo, pdf, pgf, ps, svg, template
Expand Down
199 changes: 199 additions & 0 deletions lib/matplotlib/backends/backend_legacymac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import os

import matplotlib as mpl
from matplotlib import _api, cbook
from matplotlib._pylab_helpers import Gcf
from . import _macosx
from .backend_agg import FigureCanvasAgg
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
CloseEvent, ResizeEvent, TimerBase, _allow_interrupt)


class TimerLegacyMac(_macosx.Timer, TimerBase):
"""Subclass of `.TimerBase` using CFRunLoop timer events."""
# completely implemented at the C-level (in _macosx.Timer)


def _allow_interrupt_macos():
"""A context manager that allows terminating a plot by sending a SIGINT."""
return _allow_interrupt(
lambda rsock: _macosx.wake_on_fd_write(rsock.fileno()), _macosx.stop)


class FigureCanvasLegacyMac(FigureCanvasAgg, _macosx.FigureCanvas, FigureCanvasBase):
# docstring inherited

# Ideally this class would be `class FCMacAgg(FCAgg, FCMac)`
# (FC=FigureCanvas) where FCMac would be an ObjC-implemented mac-specific
# class also inheriting from FCBase (this is the approach with other GUI
# toolkits). However, writing an extension type inheriting from a Python
# base class is slightly tricky (the extension type must be a heap type),
# and we can just as well lift the FCBase base up one level, keeping it *at
# the end* to have the right method resolution order.

# Events such as button presses, mouse movements, and key presses are
# handled in C and events (MouseEvent, etc.) are triggered from there.

required_interactive_framework = "macosx"
_timer_cls = TimerLegacyMac
manager_class = _api.classproperty(lambda cls: FigureManagerLegacyMac)

def __init__(self, figure):
super().__init__(figure=figure)
self._draw_pending = False
self._is_drawing = False
# Keep track of the timers that are alive
self._timers = set()

def draw(self):
"""Render the figure and update the macosx canvas."""
# The renderer draw is done here; delaying causes problems with code
# that uses the result of the draw() to update plot elements.
if self._is_drawing:
return
with cbook._setattr_cm(self, _is_drawing=True):
super().draw()
self.update()

def draw_idle(self):
# docstring inherited
if not (getattr(self, '_draw_pending', False) or
getattr(self, '_is_drawing', False)):
self._draw_pending = True
# Add a singleshot timer to the eventloop that will call back
# into the Python method _draw_idle to take care of the draw
self._single_shot_timer(self._draw_idle)

def _single_shot_timer(self, callback):
"""Add a single shot timer with the given callback"""
def callback_func(callback, timer):
callback()
self._timers.remove(timer)
timer = self.new_timer(interval=0)
timer.single_shot = True
timer.add_callback(callback_func, callback, timer)
self._timers.add(timer)
timer.start()

def _draw_idle(self):
"""
Draw method for singleshot timer

This draw method can be added to a singleshot timer, which can
accumulate draws while the eventloop is spinning. This method will
then only draw the first time and short-circuit the others.
"""
with self._idle_draw_cntx():
if not self._draw_pending:
# Short-circuit because our draw request has already been
# taken care of
return
self._draw_pending = False
self.draw()

def blit(self, bbox=None):
# docstring inherited
super().blit(bbox)
self.update()

def resize(self, width, height):
# Size from macOS is logical pixels, dpi is physical.
scale = self.figure.dpi / self.device_pixel_ratio
width /= scale
height /= scale
self.figure.set_size_inches(width, height, forward=False)
ResizeEvent("resize_event", self)._process()
self.draw_idle()

def start_event_loop(self, timeout=0):
# docstring inherited
# Set up a SIGINT handler to allow terminating a plot via CTRL-C.
with _allow_interrupt_macos():
self._start_event_loop(timeout=timeout) # Forward to ObjC implementation.


class NavigationToolbar2LegacyMac(_macosx.NavigationToolbar2, NavigationToolbar2):

def __init__(self, canvas):
data_path = cbook._get_data_path('images')
_, tooltips, image_names, _ = zip(*NavigationToolbar2.toolitems)
_macosx.NavigationToolbar2.__init__(
self, canvas,
tuple(str(data_path / image_name) + ".pdf"
for image_name in image_names if image_name is not None),
tuple(tooltip for tooltip in tooltips if tooltip is not None))
NavigationToolbar2.__init__(self, canvas)

def draw_rubberband(self, event, x0, y0, x1, y1):
self.canvas.set_rubberband(int(x0), int(y0), int(x1), int(y1))

def remove_rubberband(self):
self.canvas.remove_rubberband()

def save_figure(self, *args):
directory = os.path.expanduser(mpl.rcParams['savefig.directory'])
filename = _macosx.choose_save_file('Save the figure',
directory,
self.canvas.get_default_filename())
if filename is None: # Cancel
return
# Save dir for next time, unless empty str (which means use cwd).
if mpl.rcParams['savefig.directory']:
mpl.rcParams['savefig.directory'] = os.path.dirname(filename)
self.canvas.figure.savefig(filename)
return filename


class FigureManagerLegacyMac(_macosx.FigureManager, FigureManagerBase):
_toolbar2_class = NavigationToolbar2LegacyMac

def __init__(self, canvas, num):
self._shown = False
_macosx.FigureManager.__init__(self, canvas)
icon_path = str(cbook._get_data_path('images/matplotlib.pdf'))
_macosx.FigureManager.set_icon(icon_path)
FigureManagerBase.__init__(self, canvas, num)
self._set_window_mode(mpl.rcParams["macosx.window_mode"])
if self.toolbar is not None:
self.toolbar.update()
if mpl.is_interactive():
self.show()
self.canvas.draw_idle()

def _handle_window_will_close(self):
CloseEvent("close_event", self.canvas)._process()

def _handle_window_should_close(self):
Gcf.destroy(self)
self.canvas.flush_events()

def destroy(self):
# We need to clear any pending timers that never fired, otherwise
# we get a memory leak from the timer callbacks holding a reference
while self.canvas._timers:
timer = self.canvas._timers.pop()
timer.stop()
super().destroy()

@classmethod
def start_main_loop(cls):
# Set up a SIGINT handler to allow terminating a plot via CTRL-C.
with _allow_interrupt_macos():
_macosx.show()

def show(self):
if self.canvas.figure.stale:
self.canvas.draw_idle()
if not self._shown:
self._show()
self._shown = True
if mpl.rcParams["figure.raise_window"]:
self._raise()


@_Backend.export
class _BackendLegacyMac(_Backend):
FigureCanvas = FigureCanvasLegacyMac
FigureManager = FigureManagerLegacyMac
mainloop = FigureManagerLegacyMac.start_main_loop
Loading
Loading