Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

""" 

`ToolManager` 

Class that makes the bridge between user interaction (key press, 

toolbar clicks, ..) and the actions in response to the user inputs. 

""" 

 

from __future__ import (absolute_import, division, print_function, 

unicode_literals) 

import six 

import warnings 

 

import matplotlib.cbook as cbook 

import matplotlib.widgets as widgets 

from matplotlib.rcsetup import validate_stringlist 

import matplotlib.backend_tools as tools 

 

 

class ToolEvent(object): 

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

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

self.name = name 

self.sender = sender 

self.tool = tool 

self.data = data 

 

 

class ToolTriggerEvent(ToolEvent): 

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

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

ToolEvent.__init__(self, name, sender, tool, data) 

self.canvasevent = canvasevent 

 

 

class ToolManagerMessageEvent(object): 

""" 

Event carrying messages from toolmanager 

 

Messages usually get displayed to the user by the toolbar 

""" 

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

self.name = name 

self.sender = sender 

self.message = message 

 

 

class ToolManager(object): 

""" 

Helper class that groups all the user interactions for a Figure 

 

Attributes 

---------- 

figure: `Figure` 

keypresslock: `widgets.LockDraw` 

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

messagelock: `widgets.LockDraw` 

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

""" 

 

def __init__(self, figure=None): 

warnings.warn('Treat the new Tool classes introduced in v1.5 as ' + 

'experimental for now, the API will likely change in ' + 

'version 2.1 and perhaps the rcParam as well') 

 

self._key_press_handler_id = None 

 

self._tools = {} 

self._keys = {} 

self._toggled = {} 

self._callbacks = cbook.CallbackRegistry() 

 

# to process keypress event 

self.keypresslock = widgets.LockDraw() 

self.messagelock = widgets.LockDraw() 

 

self._figure = None 

self.set_figure(figure) 

 

@property 

def canvas(self): 

"""Canvas managed by FigureManager""" 

if not self._figure: 

return None 

return self._figure.canvas 

 

@property 

def figure(self): 

"""Figure that holds the canvas""" 

return self._figure 

 

@figure.setter 

def figure(self, figure): 

self.set_figure(figure) 

 

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

""" 

Sets the figure to interact with the tools 

 

Parameters 

========== 

figure: `Figure` 

update_tools: bool 

Force tools to update figure 

""" 

if self._key_press_handler_id: 

self.canvas.mpl_disconnect(self._key_press_handler_id) 

self._figure = figure 

if figure: 

self._key_press_handler_id = self.canvas.mpl_connect( 

'key_press_event', self._key_press) 

if update_tools: 

for tool in self._tools.values(): 

tool.figure = figure 

 

def toolmanager_connect(self, s, func): 

""" 

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

 

Parameters 

---------- 

s : String 

Name of the event 

 

The following events are recognized 

 

- 'tool_message_event' 

- 'tool_removed_event' 

- 'tool_added_event' 

 

For every tool added a new event is created 

 

- 'tool_trigger_TOOLNAME` 

Where TOOLNAME is the id of the tool. 

 

func : function 

Function to be called with signature 

def func(event) 

""" 

return self._callbacks.connect(s, func) 

 

def toolmanager_disconnect(self, cid): 

""" 

Disconnect callback id *cid* 

 

Example usage:: 

 

cid = toolmanager.toolmanager_connect('tool_trigger_zoom', 

on_press) 

#...later 

toolmanager.toolmanager_disconnect(cid) 

""" 

return self._callbacks.disconnect(cid) 

 

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

""" Emit a `ToolManagerMessageEvent`""" 

if sender is None: 

sender = self 

 

s = 'tool_message_event' 

event = ToolManagerMessageEvent(s, sender, message) 

self._callbacks.process(s, event) 

 

@property 

def active_toggle(self): 

"""Currently toggled tools""" 

 

return self._toggled 

 

def get_tool_keymap(self, name): 

""" 

Get the keymap associated with the specified tool 

 

Parameters 

---------- 

name : string 

Name of the Tool 

 

Returns 

------- 

list : list of keys associated with the Tool 

""" 

 

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

return keys 

 

def _remove_keys(self, name): 

for k in self.get_tool_keymap(name): 

del self._keys[k] 

 

def update_keymap(self, name, *keys): 

""" 

Set the keymap to associate with the specified tool 

 

Parameters 

---------- 

name : string 

Name of the Tool 

keys : keys to associate with the Tool 

""" 

 

if name not in self._tools: 

raise KeyError('%s not in Tools' % name) 

 

self._remove_keys(name) 

 

for key in keys: 

for k in validate_stringlist(key): 

if k in self._keys: 

warnings.warn('Key %s changed from %s to %s' % 

(k, self._keys[k], name)) 

self._keys[k] = name 

 

def remove_tool(self, name): 

""" 

Remove tool from `ToolManager` 

 

Parameters 

---------- 

name : string 

Name of the Tool 

""" 

 

tool = self.get_tool(name) 

tool.destroy() 

 

# If is a toggle tool and toggled, untoggle 

if getattr(tool, 'toggled', False): 

self.trigger_tool(tool, 'toolmanager') 

 

self._remove_keys(name) 

 

s = 'tool_removed_event' 

event = ToolEvent(s, self, tool) 

self._callbacks.process(s, event) 

 

del self._tools[name] 

 

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

""" 

Add *tool* to `ToolManager` 

 

If successful adds a new event `tool_trigger_name` where **name** is 

the **name** of the tool, this event is fired everytime 

the tool is triggered. 

 

Parameters 

---------- 

name : str 

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

tool : class_like, i.e. str or type 

Reference to find the class of the Tool to added. 

 

Notes 

----- 

args and kwargs get passed directly to the tools constructor. 

 

See Also 

-------- 

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

""" 

 

tool_cls = self._get_cls_to_instantiate(tool) 

if not tool_cls: 

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

 

if name in self._tools: 

warnings.warn('A "Tool class" with the same name already exists, ' 

'not added') 

return self._tools[name] 

 

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

self._tools[name] = tool_obj 

 

if tool_cls.default_keymap is not None: 

self.update_keymap(name, tool_cls.default_keymap) 

 

# For toggle tools init the radio_group in self._toggled 

if isinstance(tool_obj, tools.ToolToggleBase): 

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

# of all toggled tools in this group 

if tool_obj.radio_group is None: 

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

else: 

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

 

# If initially toggled 

if tool_obj.toggled: 

self._handle_toggle(tool_obj, None, None, None) 

tool_obj.set_figure(self.figure) 

 

self._tool_added_event(tool_obj) 

return tool_obj 

 

def _tool_added_event(self, tool): 

s = 'tool_added_event' 

event = ToolEvent(s, self, tool) 

self._callbacks.process(s, event) 

 

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

""" 

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

Called from trigger_tool 

 

Parameters 

---------- 

tool: Tool object 

sender: object 

Object that wishes to trigger the tool 

canvasevent : Event 

Original Canvas event or None 

data : Object 

Extra data to pass to the tool when triggering 

""" 

 

radio_group = tool.radio_group 

# radio_group None is not mutually exclusive 

# just keep track of toggled tools in this group 

if radio_group is None: 

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

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

else: 

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

return 

 

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

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

toggled = None 

# If no tool was toggled in the radio_group 

# toggle it 

elif self._toggled[radio_group] is None: 

toggled = tool.name 

# Other tool in the radio_group is toggled 

else: 

# Untoggle previously toggled tool 

self.trigger_tool(self._toggled[radio_group], 

self, 

canvasevent, 

data) 

toggled = tool.name 

 

# Keep track of the toggled tool in the radio_group 

self._toggled[radio_group] = toggled 

 

def _get_cls_to_instantiate(self, callback_class): 

# Find the class that corresponds to the tool 

if isinstance(callback_class, six.string_types): 

# FIXME: make more complete searching structure 

if callback_class in globals(): 

callback_class = globals()[callback_class] 

else: 

mod = 'backend_tools' 

current_module = __import__(mod, 

globals(), locals(), [mod], 1) 

 

callback_class = getattr(current_module, callback_class, False) 

if callable(callback_class): 

return callback_class 

else: 

return None 

 

def trigger_tool(self, name, sender=None, canvasevent=None, 

data=None): 

""" 

Trigger a tool and emit the tool_trigger_[name] event 

 

Parameters 

---------- 

name : string 

Name of the tool 

sender: object 

Object that wishes to trigger the tool 

canvasevent : Event 

Original Canvas event or None 

data : Object 

Extra data to pass to the tool when triggering 

""" 

tool = self.get_tool(name) 

if tool is None: 

return 

 

if sender is None: 

sender = self 

 

self._trigger_tool(name, sender, canvasevent, data) 

 

s = 'tool_trigger_%s' % name 

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

self._callbacks.process(s, event) 

 

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

""" 

Trigger on a tool 

 

Method to actually trigger the tool 

""" 

tool = self.get_tool(name) 

 

if isinstance(tool, tools.ToolToggleBase): 

self._handle_toggle(tool, sender, canvasevent, data) 

 

# Important!!! 

# This is where the Tool object gets triggered 

tool.trigger(sender, canvasevent, data) 

 

def _key_press(self, event): 

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

return 

 

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

if name is None: 

return 

self.trigger_tool(name, canvasevent=event) 

 

@property 

def tools(self): 

"""Return the tools controlled by `ToolManager`""" 

 

return self._tools 

 

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

""" 

Return the tool object, also accepts the actual tool for convenience 

 

Parameters 

---------- 

name : str, ToolBase 

Name of the tool, or the tool itself 

warn : bool, optional 

If this method should give warnings. 

""" 

if isinstance(name, tools.ToolBase) and name.name in self._tools: 

return name 

if name not in self._tools: 

if warn: 

warnings.warn("ToolManager does not control tool %s" % name) 

return None 

return self._tools[name]