Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/backend_managers.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

149 statements  

1from matplotlib import _api, backend_tools, cbook, widgets 

2 

3 

4class ToolEvent: 

5 """Event for tool manipulation (add/remove).""" 

6 def __init__(self, name, sender, tool, data=None): 

7 self.name = name 

8 self.sender = sender 

9 self.tool = tool 

10 self.data = data 

11 

12 

13class ToolTriggerEvent(ToolEvent): 

14 """Event to inform that a tool has been triggered.""" 

15 def __init__(self, name, sender, tool, canvasevent=None, data=None): 

16 super().__init__(name, sender, tool, data) 

17 self.canvasevent = canvasevent 

18 

19 

20class ToolManagerMessageEvent: 

21 """ 

22 Event carrying messages from toolmanager. 

23 

24 Messages usually get displayed to the user by the toolbar. 

25 """ 

26 def __init__(self, name, sender, message): 

27 self.name = name 

28 self.sender = sender 

29 self.message = message 

30 

31 

32class ToolManager: 

33 """ 

34 Manager for actions triggered by user interactions (key press, toolbar 

35 clicks, ...) on a Figure. 

36 

37 Attributes 

38 ---------- 

39 figure : `.Figure` 

40 keypresslock : `~matplotlib.widgets.LockDraw` 

41 `.LockDraw` object to know if the `canvas` key_press_event is locked. 

42 messagelock : `~matplotlib.widgets.LockDraw` 

43 `.LockDraw` object to know if the message is available to write. 

44 """ 

45 

46 def __init__(self, figure=None): 

47 

48 self._key_press_handler_id = None 

49 

50 self._tools = {} 

51 self._keys = {} 

52 self._toggled = {} 

53 self._callbacks = cbook.CallbackRegistry() 

54 

55 # to process keypress event 

56 self.keypresslock = widgets.LockDraw() 

57 self.messagelock = widgets.LockDraw() 

58 

59 self._figure = None 

60 self.set_figure(figure) 

61 

62 @property 

63 def canvas(self): 

64 """Canvas managed by FigureManager.""" 

65 if not self._figure: 

66 return None 

67 return self._figure.canvas 

68 

69 @property 

70 def figure(self): 

71 """Figure that holds the canvas.""" 

72 return self._figure 

73 

74 @figure.setter 

75 def figure(self, figure): 

76 self.set_figure(figure) 

77 

78 def set_figure(self, figure, update_tools=True): 

79 """ 

80 Bind the given figure to the tools. 

81 

82 Parameters 

83 ---------- 

84 figure : `.Figure` 

85 update_tools : bool, default: True 

86 Force tools to update figure. 

87 """ 

88 if self._key_press_handler_id: 

89 self.canvas.mpl_disconnect(self._key_press_handler_id) 

90 self._figure = figure 

91 if figure: 

92 self._key_press_handler_id = self.canvas.mpl_connect( 

93 'key_press_event', self._key_press) 

94 if update_tools: 

95 for tool in self._tools.values(): 

96 tool.figure = figure 

97 

98 def toolmanager_connect(self, s, func): 

99 """ 

100 Connect event with string *s* to *func*. 

101 

102 Parameters 

103 ---------- 

104 s : str 

105 The name of the event. The following events are recognized: 

106 

107 - 'tool_message_event' 

108 - 'tool_removed_event' 

109 - 'tool_added_event' 

110 

111 For every tool added a new event is created 

112 

113 - 'tool_trigger_TOOLNAME', where TOOLNAME is the id of the tool. 

114 

115 func : callable 

116 Callback function for the toolmanager event with signature:: 

117 

118 def func(event: ToolEvent) -> Any 

119 

120 Returns 

121 ------- 

122 cid 

123 The callback id for the connection. This can be used in 

124 `.toolmanager_disconnect`. 

125 """ 

126 return self._callbacks.connect(s, func) 

127 

128 def toolmanager_disconnect(self, cid): 

129 """ 

130 Disconnect callback id *cid*. 

131 

132 Example usage:: 

133 

134 cid = toolmanager.toolmanager_connect('tool_trigger_zoom', onpress) 

135 #...later 

136 toolmanager.toolmanager_disconnect(cid) 

137 """ 

138 return self._callbacks.disconnect(cid) 

139 

140 def message_event(self, message, sender=None): 

141 """Emit a `ToolManagerMessageEvent`.""" 

142 if sender is None: 

143 sender = self 

144 

145 s = 'tool_message_event' 

146 event = ToolManagerMessageEvent(s, sender, message) 

147 self._callbacks.process(s, event) 

148 

149 @property 

150 def active_toggle(self): 

151 """Currently toggled tools.""" 

152 return self._toggled 

153 

154 def get_tool_keymap(self, name): 

155 """ 

156 Return the keymap associated with the specified tool. 

157 

158 Parameters 

159 ---------- 

160 name : str 

161 Name of the Tool. 

162 

163 Returns 

164 ------- 

165 list of str 

166 List of keys associated with the tool. 

167 """ 

168 

169 keys = [k for k, i in self._keys.items() if i == name] 

170 return keys 

171 

172 def _remove_keys(self, name): 

173 for k in self.get_tool_keymap(name): 

174 del self._keys[k] 

175 

176 def update_keymap(self, name, key): 

177 """ 

178 Set the keymap to associate with the specified tool. 

179 

180 Parameters 

181 ---------- 

182 name : str 

183 Name of the Tool. 

184 key : str or list of str 

185 Keys to associate with the tool. 

186 """ 

187 if name not in self._tools: 

188 raise KeyError(f'{name!r} not in Tools') 

189 self._remove_keys(name) 

190 if isinstance(key, str): 

191 key = [key] 

192 for k in key: 

193 if k in self._keys: 

194 _api.warn_external( 

195 f'Key {k} changed from {self._keys[k]} to {name}') 

196 self._keys[k] = name 

197 

198 def remove_tool(self, name): 

199 """ 

200 Remove tool named *name*. 

201 

202 Parameters 

203 ---------- 

204 name : str 

205 Name of the tool. 

206 """ 

207 tool = self.get_tool(name) 

208 if getattr(tool, 'toggled', False): # If it's a toggled toggle tool, untoggle 

209 self.trigger_tool(tool, 'toolmanager') 

210 self._remove_keys(name) 

211 event = ToolEvent('tool_removed_event', self, tool) 

212 self._callbacks.process(event.name, event) 

213 del self._tools[name] 

214 

215 def add_tool(self, name, tool, *args, **kwargs): 

216 """ 

217 Add *tool* to `ToolManager`. 

218 

219 If successful, adds a new event ``tool_trigger_{name}`` where 

220 ``{name}`` is the *name* of the tool; the event is fired every time the 

221 tool is triggered. 

222 

223 Parameters 

224 ---------- 

225 name : str 

226 Name of the tool, treated as the ID, has to be unique. 

227 tool : type 

228 Class of the tool to be added. A subclass will be used 

229 instead if one was registered for the current canvas class. 

230 *args, **kwargs 

231 Passed to the *tool*'s constructor. 

232 

233 See Also 

234 -------- 

235 matplotlib.backend_tools.ToolBase : The base class for tools. 

236 """ 

237 

238 tool_cls = backend_tools._find_tool_class(type(self.canvas), tool) 

239 if not tool_cls: 

240 raise ValueError('Impossible to find class for %s' % str(tool)) 

241 

242 if name in self._tools: 

243 _api.warn_external('A "Tool class" with the same name already ' 

244 'exists, not added') 

245 return self._tools[name] 

246 

247 tool_obj = tool_cls(self, name, *args, **kwargs) 

248 self._tools[name] = tool_obj 

249 

250 if tool_obj.default_keymap is not None: 

251 self.update_keymap(name, tool_obj.default_keymap) 

252 

253 # For toggle tools init the radio_group in self._toggled 

254 if isinstance(tool_obj, backend_tools.ToolToggleBase): 

255 # None group is not mutually exclusive, a set is used to keep track 

256 # of all toggled tools in this group 

257 if tool_obj.radio_group is None: 

258 self._toggled.setdefault(None, set()) 

259 else: 

260 self._toggled.setdefault(tool_obj.radio_group, None) 

261 

262 # If initially toggled 

263 if tool_obj.toggled: 

264 self._handle_toggle(tool_obj, None, None) 

265 tool_obj.set_figure(self.figure) 

266 

267 event = ToolEvent('tool_added_event', self, tool_obj) 

268 self._callbacks.process(event.name, event) 

269 

270 return tool_obj 

271 

272 def _handle_toggle(self, tool, canvasevent, data): 

273 """ 

274 Toggle tools, need to untoggle prior to using other Toggle tool. 

275 Called from trigger_tool. 

276 

277 Parameters 

278 ---------- 

279 tool : `.ToolBase` 

280 canvasevent : Event 

281 Original Canvas event or None. 

282 data : object 

283 Extra data to pass to the tool when triggering. 

284 """ 

285 

286 radio_group = tool.radio_group 

287 # radio_group None is not mutually exclusive 

288 # just keep track of toggled tools in this group 

289 if radio_group is None: 

290 if tool.name in self._toggled[None]: 

291 self._toggled[None].remove(tool.name) 

292 else: 

293 self._toggled[None].add(tool.name) 

294 return 

295 

296 # If the tool already has a toggled state, untoggle it 

297 if self._toggled[radio_group] == tool.name: 

298 toggled = None 

299 # If no tool was toggled in the radio_group 

300 # toggle it 

301 elif self._toggled[radio_group] is None: 

302 toggled = tool.name 

303 # Other tool in the radio_group is toggled 

304 else: 

305 # Untoggle previously toggled tool 

306 self.trigger_tool(self._toggled[radio_group], 

307 self, 

308 canvasevent, 

309 data) 

310 toggled = tool.name 

311 

312 # Keep track of the toggled tool in the radio_group 

313 self._toggled[radio_group] = toggled 

314 

315 def trigger_tool(self, name, sender=None, canvasevent=None, data=None): 

316 """ 

317 Trigger a tool and emit the ``tool_trigger_{name}`` event. 

318 

319 Parameters 

320 ---------- 

321 name : str 

322 Name of the tool. 

323 sender : object 

324 Object that wishes to trigger the tool. 

325 canvasevent : Event 

326 Original Canvas event or None. 

327 data : object 

328 Extra data to pass to the tool when triggering. 

329 """ 

330 tool = self.get_tool(name) 

331 if tool is None: 

332 return 

333 

334 if sender is None: 

335 sender = self 

336 

337 if isinstance(tool, backend_tools.ToolToggleBase): 

338 self._handle_toggle(tool, canvasevent, data) 

339 

340 tool.trigger(sender, canvasevent, data) # Actually trigger Tool. 

341 

342 s = 'tool_trigger_%s' % name 

343 event = ToolTriggerEvent(s, sender, tool, canvasevent, data) 

344 self._callbacks.process(s, event) 

345 

346 def _key_press(self, event): 

347 if event.key is None or self.keypresslock.locked(): 

348 return 

349 

350 name = self._keys.get(event.key, None) 

351 if name is None: 

352 return 

353 self.trigger_tool(name, canvasevent=event) 

354 

355 @property 

356 def tools(self): 

357 """A dict mapping tool name -> controlled tool.""" 

358 return self._tools 

359 

360 def get_tool(self, name, warn=True): 

361 """ 

362 Return the tool object with the given name. 

363 

364 For convenience, this passes tool objects through. 

365 

366 Parameters 

367 ---------- 

368 name : str or `.ToolBase` 

369 Name of the tool, or the tool itself. 

370 warn : bool, default: True 

371 Whether a warning should be emitted it no tool with the given name 

372 exists. 

373 

374 Returns 

375 ------- 

376 `.ToolBase` or None 

377 The tool or None if no tool with the given name exists. 

378 """ 

379 if (isinstance(name, backend_tools.ToolBase) 

380 and name.name in self._tools): 

381 return name 

382 if name not in self._tools: 

383 if warn: 

384 _api.warn_external( 

385 f"ToolManager does not control tool {name!r}") 

386 return None 

387 return self._tools[name]