197 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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,
 | |
|     ResizeEvent, TimerBase, _allow_interrupt)
 | |
| 
 | |
| 
 | |
| class TimerMac(_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 FigureCanvasMac(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 = TimerMac
 | |
|     manager_class = _api.classproperty(lambda cls: FigureManagerMac)
 | |
| 
 | |
|     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 NavigationToolbar2Mac(_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 FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
 | |
|     _toolbar2_class = NavigationToolbar2Mac
 | |
| 
 | |
|     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 _close_button_pressed(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 _BackendMac(_Backend):
 | |
|     FigureCanvas = FigureCanvasMac
 | |
|     FigureManager = FigureManagerMac
 | |
|     mainloop = FigureManagerMac.start_main_loop
 |