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# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20140907131341.18707: * @file ../plugins/qt_tree.py 

4#@@first 

5"""Leo's Qt tree class.""" 

6#@+<< imports >> 

7#@+node:ekr.20140907131341.18709: ** << imports >> (qt_tree.py) 

8import re 

9import time 

10from typing import Any, List 

11from leo.core.leoQt import isQt6, QtCore, QtGui, QtWidgets 

12from leo.core.leoQt import EndEditHint, Format, ItemFlag, KeyboardModifier 

13from leo.core import leoGlobals as g 

14from leo.core import leoFrame 

15from leo.core import leoNodes 

16from leo.core import leoPlugins # Uses leoPlugins.TryNext. 

17from leo.plugins import qt_text 

18#@-<< imports >> 

19#@+others 

20#@+node:ekr.20160514120051.1: ** class LeoQtTree 

21class LeoQtTree(leoFrame.LeoTree): 

22 """Leo Qt tree class""" 

23 #@+others 

24 #@+node:ekr.20110605121601.18404: *3* qtree.Birth 

25 #@+node:ekr.20110605121601.18405: *4* qtree.__init__ 

26 def __init__(self, c, frame): 

27 """Ctor for the LeoQtTree class.""" 

28 super().__init__(frame) 

29 self.c = c 

30 # Widget independent status ivars... 

31 self.prev_v = None 

32 self.redrawCount = 0 # Count for debugging. 

33 self.revertHeadline = None # Previous headline text for abortEditLabel. 

34 self.busy = False 

35 # Debugging... 

36 self.traceCallersFlag = False # Enable traceCallers method. 

37 # Associating items with position and vnodes... 

38 self.items = [] 

39 self.item2positionDict = {} 

40 self.item2vnodeDict = {} 

41 self.nodeIconsDict = {} # keys are gnx, values are declutter generated icons 

42 self.position2itemDict = {} 

43 self.vnode2itemsDict = {} # values are lists of items. 

44 self.editWidgetsDict = {} # keys are native edit widgets, values are wrappers. 

45 self.reloadSettings() 

46 # Components. 

47 self.canvas = self # An official ivar used by Leo's core. 

48 self.headlineWrapper = qt_text.QHeadlineWrapper # This is a class. 

49 self.treeWidget = w = frame.top.treeWidget # An internal ivar. 

50 # w is a LeoQTreeWidget, a subclass of QTreeWidget. 

51 # 

52 # "declutter", node appearance tweaking 

53 self.declutter_patterns = None # list of pairs of patterns for decluttering 

54 self.declutter_data = {} 

55 self.loaded_images = {} 

56 if 0: # Drag and drop 

57 w.setDragEnabled(True) 

58 w.viewport().setAcceptDrops(True) 

59 w.showDropIndicator = True 

60 w.setAcceptDrops(True) 

61 w.setDragDropMode(w.InternalMove) 

62 if 1: # Does not work 

63 

64 def dropMimeData(self, data, action, row, col, parent): 

65 g.trace() 

66 

67 # w.dropMimeData = dropMimeData 

68 

69 def mimeData(self, indexes): 

70 g.trace() 

71 

72 # Early inits... 

73 

74 try: 

75 w.headerItem().setHidden(True) 

76 except Exception: 

77 pass 

78 n = c.config.getInt('icon-height') or 16 

79 w.setIconSize(QtCore.QSize(160, n)) 

80 #@+node:ekr.20110605121601.17866: *4* qtree.get_name 

81 def getName(self): 

82 """Return the name of this widget: must start with "canvas".""" 

83 return 'canvas(tree)' 

84 #@+node:ekr.20110605121601.18406: *4* qtree.initAfterLoad 

85 def initAfterLoad(self): 

86 """Do late-state inits.""" 

87 # Called by Leo's core. 

88 c = self.c 

89 # w = c.frame.top 

90 tw = self.treeWidget 

91 tw.itemDoubleClicked.connect(self.onItemDoubleClicked) 

92 tw.itemClicked.connect(self.onItemClicked) 

93 tw.itemSelectionChanged.connect(self.onTreeSelect) 

94 tw.itemCollapsed.connect(self.onItemCollapsed) 

95 tw.itemExpanded.connect(self.onItemExpanded) 

96 tw.customContextMenuRequested.connect(self.onContextMenu) 

97 # tw.onItemChanged.connect(self.onItemChanged) 

98 g.app.gui.setFilter(c, tw, self, tag='tree') 

99 # 2010/01/24: Do not set this here. 

100 # The read logic sets c.changed to indicate nodes have changed. 

101 # c.clearChanged() 

102 #@+node:ekr.20110605121601.17871: *4* qtree.reloadSettings 

103 def reloadSettings(self): 

104 """LeoQtTree.""" 

105 c = self.c 

106 self.auto_edit = c.config.getBool('single-click-auto-edits-headline', False) 

107 self.enable_drag_messages = c.config.getBool("enable-drag-messages") 

108 self.select_all_text_when_editing_headlines = c.config.getBool( 

109 'select_all_text_when_editing_headlines') 

110 self.stayInTree = c.config.getBool('stayInTreeAfterSelect') 

111 self.use_chapters = c.config.getBool('use-chapters') 

112 self.use_declutter = c.config.getBool('tree-declutter', default=False) 

113 #@+node:ekr.20110605121601.17940: *4* qtree.wrapQLineEdit 

114 def wrapQLineEdit(self, w): 

115 """A wretched kludge for MacOs k.masterMenuHandler.""" 

116 c = self.c 

117 if isinstance(w, QtWidgets.QLineEdit): 

118 wrapper = self.edit_widget(c.p) 

119 else: 

120 wrapper = w 

121 return wrapper 

122 #@+node:ekr.20110605121601.17868: *3* qtree.Debugging & tracing 

123 def error(self, s): 

124 if not g.unitTesting: 

125 g.trace('LeoQtTree Error: ', s, g.callers()) 

126 

127 def traceItem(self, item): 

128 if item: 

129 # A QTreeWidgetItem. 

130 return f"item {id(item)}: {self.getItemText(item)}" 

131 return '<no item>' 

132 

133 def traceCallers(self): 

134 if self.traceCallersFlag: 

135 return g.callers(5, excludeCaller=True) 

136 return '' 

137 #@+node:ekr.20110605121601.17872: *3* qtree.Drawing 

138 #@+node:ekr.20110605121601.18408: *4* qtree.clear 

139 def clear(self): 

140 """Clear all widgets in the tree.""" 

141 w = self.treeWidget 

142 w.clear() 

143 #@+node:ekr.20180810052056.1: *4* qtree.drawVisible & helpers (not used) 

144 def drawVisible(self, p): 

145 """ 

146 Add only the visible nodes to the outline. 

147 

148 Not used, as this causes scrolling issues. 

149 """ 

150 t1 = time.process_time() 

151 c = self.c 

152 parents: List[Any] = [] 

153 # Clear the widget. 

154 w = self.treeWidget 

155 w.clear() 

156 # Clear the dicts. 

157 self.initData() 

158 if c.hoistStack: 

159 first_p = c.hoistStack[-1].p 

160 target_p = first_p.nodeAfterTree().visBack(c) 

161 else: 

162 first_p = c.rootPosition() 

163 target_p = None 

164 n = 0 

165 for p in self.yieldVisible(first_p, target_p): 

166 n += 1 

167 level = p.level() 

168 parent_item = w if level == 0 else parents[level - 1] 

169 item = QtWidgets.QTreeWidgetItem(parent_item) 

170 item.setFlags(item.flags() | ItemFlag.ItemIsEditable) 

171 item.setChildIndicatorPolicy( 

172 item.ShowIndicator if p.hasChildren() 

173 else item.DontShowIndicator) 

174 item.setExpanded(bool(p.hasChildren() and p.isExpanded())) 

175 self.items.append(item) 

176 # Update parents. 

177 parents = [] if level == 0 else parents[:level] 

178 parents.append(item) 

179 # Update the dicts. 

180 itemHash = self.itemHash(item) 

181 self.item2positionDict[itemHash] = p.copy() 

182 self.item2vnodeDict[itemHash] = p.v 

183 self.position2itemDict[p.key()] = item 

184 d = self.vnode2itemsDict 

185 v = p.v 

186 aList = d.get(v, []) 

187 aList.append(item) 

188 d[v] = aList 

189 # Enter the headline. 

190 item.setText(0, p.h) 

191 if self.use_declutter: 

192 item._real_text = p.h 

193 # Draw the icon. 

194 v.iconVal = v.computeIcon() 

195 icon = self.getCompositeIconImage(p, v.iconVal) 

196 if icon: 

197 self.setItemIcon(item, icon) 

198 # Set current item. 

199 if p == c.p: 

200 w.setCurrentItem(item) 

201 # Useful, for now. 

202 t2 = time.process_time() 

203 if t2 - t1 > 0.1: 

204 g.trace(f"{n} nodes, {t2 - t1:5.2f} sec") 

205 #@+node:ekr.20180810052056.2: *5* qtree.yieldVisible (not used) 

206 def yieldVisible(self, first_p, target_p=None): 

207 """ 

208 A generator yielding positions from first_p to target_p. 

209 """ 

210 c = self.c 

211 p = first_p.copy() 

212 yield p 

213 while p: 

214 if p == target_p: 

215 return 

216 v = p.v 

217 if (v.children and ( 

218 # Use slower test for clones: 

219 len(v.parents) > 1 and p in v.expandedPositions or 

220 # Use a quick test for non-clones: 

221 len(v.parents) <= 1 and (v.statusBits & v.expandedBit) != 0 

222 )): 

223 # p.moveToFirstChild() 

224 p.stack.append((v, p._childIndex),) 

225 p.v = v.children[0] 

226 p._childIndex = 0 

227 yield p 

228 continue 

229 # if p.hasNext(): 

230 parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode 

231 if p._childIndex + 1 < len(parent_v.children): 

232 # p.moveToNext() 

233 p._childIndex += 1 

234 p.v = parent_v.children[p._childIndex] 

235 yield p 

236 continue 

237 # 

238 # A fast version of p.moveToThreadNext(). 

239 # We look for a parent with a following sibling. 

240 while p.stack: 

241 # p.moveToParent() 

242 p.v, p._childIndex = p.stack.pop() 

243 # if p.hasNext(): 

244 parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode 

245 if p._childIndex + 1 < len(parent_v.children): 

246 # p.moveToNext() 

247 p._childIndex += 1 

248 p.v = parent_v.children[p._childIndex] 

249 break # Found: moveToThreadNext() 

250 else: 

251 break # Not found. 

252 # Found moveToThreadNext() 

253 yield p 

254 continue 

255 if target_p: 

256 g.trace('NOT FOUND:', target_p.h) 

257 #@+node:ekr.20180810052056.3: *5* qtree.slowYieldVisible 

258 def slowYieldVisible(self, first_p, target_p=None): 

259 """ 

260 A generator yielding positions from first_p to target_p. 

261 """ 

262 c = self.c 

263 p = first_p.copy() 

264 while p: 

265 yield p 

266 if p == target_p: 

267 return 

268 p.moveToVisNext(c) 

269 if target_p: 

270 g.trace('NOT FOUND:', target_p.h) 

271 #@+node:ekr.20110605121601.17873: *4* qtree.full_redraw & helpers 

272 def full_redraw(self, p=None): 

273 """ 

274 Redraw all visible nodes of the tree. 

275 Preserve the vertical scrolling unless scroll is True. 

276 """ 

277 c = self.c 

278 if g.app.disable_redraw: 

279 return None 

280 if self.busy: 

281 return None 

282 # Cancel the delayed redraw request. 

283 c.requestLaterRedraw = False 

284 if not p: 

285 p = c.currentPosition() 

286 elif c.hoistStack and p.h.startswith('@chapter') and p.hasChildren(): 

287 # Make sure the current position is visible. 

288 # Part of fix of bug 875323: Hoist an @chapter node leaves a non-visible node selected. 

289 p = p.firstChild() 

290 c.frame.tree.select(p) 

291 c.setCurrentPosition(p) 

292 else: 

293 c.setCurrentPosition(p) 

294 assert not self.busy, g.callers() 

295 self.redrawCount += 1 

296 self.initData() 

297 try: 

298 self.busy = True 

299 self.drawTopTree(p) 

300 finally: 

301 self.busy = False 

302 self.setItemForCurrentPosition() 

303 return p # Return the position, which may have changed. 

304 

305 # Compatibility 

306 

307 redraw = full_redraw 

308 redraw_now = full_redraw 

309 #@+node:vitalije.20200329160945.1: *5* tree declutter code 

310 #@+node:tbrown.20150807090639.1: *6* qtree.declutter_node & helpers 

311 def declutter_node(self, c, p, item): 

312 """declutter_node - change the appearance of a node 

313 

314 :param commander c: commander containing node 

315 :param position p: position of node 

316 :param QWidgetItem item: tree node widget item 

317 

318 returns composite icon for this node 

319 """ 

320 dd = self.declutter_data 

321 iconVal = p.v.computeIcon() 

322 iconName = f'box{iconVal:02d}.png' 

323 loaded_images = self.loaded_images 

324 #@+others 

325 #@+node:vitalije.20200329153544.1: *7* sorted_icons 

326 def sorted_icons(p): 

327 """ 

328 Returns a list of icon filenames for this node. 

329 The list is sorted to owner the 'where' key of image dicts. 

330 """ 

331 icons = c.editCommands.getIconList(p) 

332 a = [x['file'] for x in icons if x['where'] == 'beforeIcon'] 

333 a.append(iconName) 

334 a.extend(x['file'] for x in icons if x['where'] == 'beforeHeadline') 

335 return a 

336 #@+node:ekr.20171122064635.1: *7* declutter_replace 

337 def declutter_replace(arg, cmd): 

338 """ 

339 Executes cmd if cmd is any replace command and returns 

340 pair (commander, s), where 'commander' corresponds 

341 to the executed replacement operation, 's' is the substituted string. 

342 If cmd is not a replacement command returns (None, None) 

343 """ 

344 # pylint: disable=undefined-loop-variable 

345 

346 replacement, s = None, None 

347 

348 if cmd == 'REPLACE': 

349 s = pattern.sub(arg, text) 

350 elif cmd == 'REPLACE-HEAD': 

351 s = text[: m.start()].rstrip() 

352 elif cmd == 'REPLACE-TAIL': 

353 s = text[m.end() :].lstrip() 

354 elif cmd == 'REPLACE-REST': 

355 s = (text[:m.start] + text[m.end() :]).strip() 

356 

357 # 's' is string when 'cmd' is recognised 

358 # and is None otherwise 

359 if isinstance(s, str): 

360 # Save the operation 

361 replacement = lambda item, s: item.setText(0, s) 

362 # ... and apply it 

363 replacement(item, s) 

364 

365 return replacement, s 

366 #@+node:ekr.20171122055719.1: *7* declutter_style 

367 def declutter_style(arg, cmd): 

368 """ 

369 Handles style options and returns pair '(commander, param)', 

370 where 'commander' is the applied style-modifying operation, 

371 param - the saved argument of that operation. 

372 Returns (None, param) if 'cmd' is not a style option. 

373 """ 

374 # pylint: disable=function-redefined 

375 param = c.styleSheetManager.expand_css_constants(arg).split()[0] 

376 modifier = None 

377 if cmd == 'ICON': 

378 def modifier(item, param): 

379 # Does not fit well this function. And we cannot 

380 # wrap list 'new_icons' in a saved argument as 

381 # the list is recreated before each call. 

382 new_icons.append(param) 

383 elif cmd == 'BG': 

384 def modifier(item, param): 

385 item.setBackground(0, QtGui.QBrush(QtGui.QColor(param))) 

386 elif cmd == 'FG': 

387 def modifier(item, param): 

388 item.setForeground(0, QtGui.QBrush(QtGui.QColor(param))) 

389 elif cmd == 'FONT': 

390 def modifier(item, param): 

391 item.setFont(0, QtGui.QFont(param)) 

392 elif cmd == 'ITALIC': 

393 def modifier(item, param): 

394 font = item.font(0) 

395 font.setItalic(bool(int(param))) 

396 item.setFont(0, font) 

397 elif cmd == 'WEIGHT': 

398 def modifier(item, param): 

399 arg = getattr(QtGui.QFont, param, 75) 

400 font = item.font(0) 

401 font.setWeight(arg) 

402 item.setFont(0, font) 

403 elif cmd == 'PX': 

404 def modifier(item, param): 

405 font = item.font(0) 

406 font.setPixelSize(int(param)) 

407 item.setFont(0, font) 

408 elif cmd == 'PT': 

409 def modifier(item, param): 

410 font = item.font(0) 

411 font.setPointSize(int(param)) 

412 item.setFont(0, font) 

413 # Apply the style update 

414 if modifier: 

415 modifier(item, param) 

416 return modifier, param 

417 #@+node:vitalije.20200327163522.1: *7* apply_declutter_rules 

418 def apply_declutter_rules(cmds): 

419 """ 

420 Applies all commands for the matched rule. Returns the list 

421 of the applied operations paired with their single parameter. 

422 """ 

423 modifiers = [] 

424 for cmd, arg in cmds: 

425 modifier, param = declutter_replace(arg, cmd) 

426 if not modifier: 

427 modifier, param = declutter_style(arg, cmd) 

428 if modifier: 

429 modifiers.append((modifier, param)) 

430 return modifiers 

431 #@+node:vitalije.20200329162015.1: *7* preload_images 

432 def preload_images(): 

433 for f in new_icons: 

434 if f not in loaded_images: 

435 loaded_images[f] = g.app.gui.getImageImage(f) 

436 #@-others 

437 if (p.h, iconVal) in dd: 

438 # Apply saved adjustments to the text and to the _style_ 

439 # of the node 

440 new_icons, modifiers_and_args = dd[(p.h, iconVal)] 

441 for modifier, arg in modifiers_and_args: 

442 modifier(item, arg) 

443 

444 new_icons = sorted_icons(p) + new_icons 

445 else: 

446 text = p.h 

447 new_icons = [] 

448 modifiers_and_args = [] 

449 for pattern, cmds in self.get_declutter_patterns(): 

450 m = pattern.match(text) or pattern.search(text) 

451 if m: 

452 modifiers_and_args.extend(apply_declutter_rules(cmds)) 

453 

454 # Save the lists of the icons and the adjusting operations 

455 # for future reuse. 

456 dd[(p.h, iconVal)] = new_icons, modifiers_and_args 

457 new_icons = sorted_icons(p) + new_icons 

458 preload_images() 

459 self.nodeIconsDict[p.gnx] = new_icons 

460 h = ':'.join(new_icons) 

461 icon = g.app.gui.iconimages.get(h) 

462 if not icon: 

463 preload_images() 

464 images = [loaded_images.get(x) for x in new_icons] 

465 icon = self.make_composite_icon(images) 

466 g.app.gui.iconimages[h] = icon 

467 return icon 

468 #@+node:vitalije.20200327162532.1: *6* qtree.get_declutter_patterns 

469 def get_declutter_patterns(self): 

470 "Initializes self.declutter_patterns from configuration and returns it" 

471 if self.declutter_patterns is not None: 

472 return self.declutter_patterns 

473 c = self.c 

474 patterns: List[Any] = [] 

475 warned = False 

476 lines = c.config.getData("tree-declutter-patterns") 

477 for line in lines: 

478 try: 

479 cmd, arg = line.split(None, 1) 

480 except ValueError: 

481 # Allow empty arg, and guard against user errors. 

482 cmd = line.strip() 

483 arg = '' 

484 if cmd.startswith('#'): 

485 pass 

486 elif cmd == 'RULE': 

487 patterns.append((re.compile(arg), [])) 

488 else: 

489 if patterns: 

490 patterns[-1][1].append((cmd, arg)) 

491 elif not warned: 

492 warned = True 

493 g.log('Declutter patterns must start with RULE*', 

494 color='error') 

495 self.declutter_patterns = patterns 

496 return patterns 

497 #@+node:ekr.20110605121601.17874: *5* qtree.drawChildren 

498 def drawChildren(self, p, parent_item): 

499 """Draw the children of p if they should be expanded.""" 

500 if not p: 

501 g.trace('can not happen: no p') 

502 return 

503 if p.hasChildren(): 

504 if p.isExpanded(): 

505 self.expandItem(parent_item) 

506 child = p.firstChild() 

507 while child: 

508 self.drawTree(child, parent_item) 

509 child.moveToNext() 

510 else: 

511 # Draw the hidden children. 

512 child = p.firstChild() 

513 while child: 

514 self.drawNode(child, parent_item) 

515 child.moveToNext() 

516 self.contractItem(parent_item) 

517 else: 

518 self.contractItem(parent_item) 

519 #@+node:ekr.20110605121601.17875: *5* qtree.drawNode 

520 def drawNode(self, p, parent_item): 

521 """Draw the node p.""" 

522 c = self.c 

523 v = p.v 

524 # Allocate the QTreeWidgetItem. 

525 item = self.createTreeItem(p, parent_item) 

526 # 

527 # Update the data structures. 

528 itemHash = self.itemHash(item) 

529 self.position2itemDict[p.key()] = item 

530 self.item2positionDict[itemHash] = p.copy() # was item 

531 self.item2vnodeDict[itemHash] = v # was item 

532 d = self.vnode2itemsDict 

533 aList = d.get(v, []) 

534 if item not in aList: 

535 aList.append(item) 

536 d[v] = aList 

537 # Set the headline and maybe the icon. 

538 self.setItemText(item, p.h) 

539 # #1310: Add a tool tip. 

540 item.setToolTip(0, p.h) 

541 if self.use_declutter: 

542 icon = self.declutter_node(c, p, item) 

543 if icon: 

544 item.setIcon(0, icon) 

545 return item 

546 # Draw the icon. 

547 v.iconVal = v.computeIcon() 

548 icon = self.getCompositeIconImage(p, v.iconVal) 

549 # **Slow**, but allows per-vnode icons. 

550 if icon: 

551 item.setIcon(0, icon) 

552 return item 

553 #@+node:ekr.20110605121601.17876: *5* qtree.drawTopTree 

554 def drawTopTree(self, p): 

555 """Draw the tree rooted at p.""" 

556 trace = 'drawing' in g.app.debug and not g.unitTesting 

557 if trace: 

558 t1 = time.process_time() 

559 c = self.c 

560 self.clear() 

561 # Draw all top-level nodes and their visible descendants. 

562 if c.hoistStack: 

563 bunch = c.hoistStack[-1] 

564 p = bunch.p 

565 h = p.h 

566 if len(c.hoistStack) == 1 and h.startswith('@chapter') and p.hasChildren(): 

567 p = p.firstChild() 

568 while p: 

569 self.drawTree(p) 

570 p.moveToNext() 

571 else: 

572 self.drawTree(p) 

573 else: 

574 p = c.rootPosition() 

575 while p: 

576 self.drawTree(p) 

577 p.moveToNext() 

578 if trace: 

579 t2 = time.process_time() 

580 g.trace(f"{t2 - t1:5.2f} sec.", g.callers(5)) 

581 #@+node:ekr.20110605121601.17877: *5* qtree.drawTree 

582 def drawTree(self, p, parent_item=None): 

583 if g.app.gui.isNullGui: 

584 return 

585 # Draw the (visible) parent node. 

586 item = self.drawNode(p, parent_item) 

587 # Draw all the visible children. 

588 self.drawChildren(p, parent_item=item) 

589 #@+node:ekr.20110605121601.17878: *5* qtree.initData 

590 def initData(self): 

591 self.item2positionDict = {} 

592 self.item2vnodeDict = {} 

593 self.position2itemDict = {} 

594 self.vnode2itemsDict = {} 

595 self.editWidgetsDict = {} 

596 #@+node:ekr.20110605121601.17880: *4* qtree.redraw_after_contract 

597 def redraw_after_contract(self, p): 

598 

599 if self.busy: 

600 return 

601 self.update_expansion(p) 

602 #@+node:ekr.20110605121601.17881: *4* qtree.redraw_after_expand 

603 def redraw_after_expand(self, p): 

604 

605 if 0: # Does not work. Newly visible nodes do not show children correctly. 

606 c = self.c 

607 c.selectPosition(p) 

608 self.update_expansion(p) 

609 else: 

610 self.full_redraw(p) 

611 # Don't try to shortcut this! 

612 #@+node:ekr.20110605121601.17882: *4* qtree.redraw_after_head_changed 

613 def redraw_after_head_changed(self): 

614 """Redraw all Qt outline items cloned to c.p.""" 

615 if self.busy: 

616 return 

617 p = self.c.p 

618 if p: 

619 h = p.h # 2010/02/09: Fix bug 518823. 

620 for item in self.vnode2items(p.v): 

621 if self.isValidItem(item): 

622 self.setItemText(item, h) 

623 # Bug fix: 2009/10/06 

624 self.redraw_after_icons_changed() 

625 #@+node:ekr.20110605121601.17883: *4* qtree.redraw_after_icons_changed 

626 def redraw_after_icons_changed(self): 

627 

628 if self.busy: 

629 return 

630 self.redrawCount += 1 # To keep a unit test happy. 

631 c = self.c 

632 try: 

633 self.busy = True 

634 # Suppress call to setHeadString in onItemChanged! 

635 self.getCurrentItem() 

636 for p in c.rootPosition().self_and_siblings(copy=False): 

637 # Updates icons in p and all visible descendants of p. 

638 self.updateVisibleIcons(p) 

639 finally: 

640 self.busy = False 

641 #@+node:ekr.20110605121601.17884: *4* qtree.redraw_after_select 

642 def redraw_after_select(self, p=None): 

643 """Redraw the entire tree when an invisible node is selected.""" 

644 if self.busy: 

645 return 

646 self.full_redraw(p) 

647 # c.redraw_after_select calls tree.select indirectly. 

648 # Do not call it again here. 

649 #@+node:ekr.20140907201613.18986: *4* qtree.repaint (not used) 

650 def repaint(self): 

651 """Repaint the widget.""" 

652 w = self.treeWidget 

653 w.repaint() 

654 w.resizeColumnToContents(0) # 2009/12/22 

655 #@+node:ekr.20180817043619.1: *4* qtree.update_expansion 

656 def update_expansion(self, p): 

657 """Update expansion bits for p, including all clones.""" 

658 c = self.c 

659 w = self.treeWidget 

660 expand = c.shouldBeExpanded(p) 

661 if 'drawing' in g.app.debug: 

662 g.trace('expand' if expand else 'contract') 

663 item = self.position2itemDict.get(p.key()) 

664 if p: 

665 try: 

666 # These generate events, which would trigger a full redraw. 

667 self.busy = True 

668 if expand: 

669 w.expandItem(item) 

670 else: 

671 w.collapseItem(item) 

672 finally: 

673 self.busy = False 

674 w.repaint() 

675 else: 

676 g.trace('NO P') 

677 c.redraw() 

678 #@+node:ekr.20110605121601.17885: *3* qtree.Event handlers 

679 #@+node:ekr.20110605121601.17887: *4* qtree.Click Box 

680 #@+node:ekr.20110605121601.17888: *5* qtree.onClickBoxClick 

681 def onClickBoxClick(self, event, p=None): 

682 if self.busy: 

683 return 

684 c = self.c 

685 g.doHook("boxclick1", c=c, p=p, event=event) 

686 g.doHook("boxclick2", c=c, p=p, event=event) 

687 c.outerUpdate() 

688 #@+node:ekr.20110605121601.17889: *5* qtree.onClickBoxRightClick 

689 def onClickBoxRightClick(self, event, p=None): 

690 if self.busy: 

691 return 

692 c = self.c 

693 g.doHook("boxrclick1", c=c, p=p, event=event) 

694 g.doHook("boxrclick2", c=c, p=p, event=event) 

695 c.outerUpdate() 

696 #@+node:ekr.20110605121601.17890: *5* qtree.onPlusBoxRightClick 

697 def onPlusBoxRightClick(self, event, p=None): 

698 if self.busy: 

699 return 

700 c = self.c 

701 g.doHook('rclick-popup', c=c, p=p, event=event, context_menu='plusbox') 

702 c.outerUpdate() 

703 #@+node:ekr.20110605121601.17891: *4* qtree.Icon Box 

704 # For Qt, there seems to be no way to trigger these events. 

705 #@+node:ekr.20110605121601.17892: *5* qtree.onIconBoxClick 

706 def onIconBoxClick(self, event, p=None): 

707 if self.busy: 

708 return 

709 c = self.c 

710 g.doHook("iconclick1", c=c, p=p, event=event) 

711 g.doHook("iconclick2", c=c, p=p, event=event) 

712 c.outerUpdate() 

713 #@+node:ekr.20110605121601.17893: *5* qtree.onIconBoxRightClick 

714 def onIconBoxRightClick(self, event, p=None): 

715 """Handle a right click in any outline widget.""" 

716 if self.busy: 

717 return 

718 c = self.c 

719 g.doHook("iconrclick1", c=c, p=p, event=event) 

720 g.doHook("iconrclick2", c=c, p=p, event=event) 

721 c.outerUpdate() 

722 #@+node:ekr.20110605121601.17894: *5* qtree.onIconBoxDoubleClick 

723 def onIconBoxDoubleClick(self, event, p=None): 

724 if self.busy: 

725 return 

726 c = self.c 

727 if not p: 

728 p = c.p 

729 if not g.doHook("icondclick1", c=c, p=p, event=event): 

730 self.endEditLabel() 

731 self.OnIconDoubleClick(p) # Call the method in the base class. 

732 g.doHook("icondclick2", c=c, p=p, event=event) 

733 c.outerUpdate() 

734 #@+node:ekr.20110605121601.18437: *4* qtree.onContextMenu 

735 def onContextMenu(self, point): 

736 """LeoQtTree: Callback for customContextMenuRequested events.""" 

737 # #1286. 

738 c, w = self.c, self.treeWidget 

739 g.app.gui.onContextMenu(c, w, point) 

740 #@+node:ekr.20110605121601.17896: *4* qtree.onItemClicked 

741 def onItemClicked(self, item, col): 

742 """Handle a click in a BaseNativeTree widget item.""" 

743 # This is called after an item is selected. 

744 if self.busy: 

745 return 

746 c = self.c 

747 try: 

748 self.busy = True 

749 p = self.item2position(item) 

750 if p: 

751 auto_edit = self.prev_v == p.v 

752 # Fix #1049. 

753 self.prev_v = p.v 

754 event = None 

755 # 

756 # Careful. We may have switched gui during unit testing. 

757 if hasattr(g.app.gui, 'qtApp'): 

758 mods = g.app.gui.qtApp.keyboardModifiers() 

759 isCtrl = bool(mods & KeyboardModifier.ControlModifier) 

760 # We could also add support for QtConst.ShiftModifier, QtConst.AltModifier 

761 # & QtConst.MetaModifier. 

762 if isCtrl: 

763 if g.doHook("iconctrlclick1", c=c, p=p, event=event) is None: 

764 c.frame.tree.OnIconCtrlClick(p) 

765 # Call the base class method. 

766 g.doHook("iconctrlclick2", c=c, p=p, event=event) 

767 else: 

768 # 2014/02/21: generate headclick1/2 instead of iconclick1/2 

769 g.doHook("headclick1", c=c, p=p, event=event) 

770 g.doHook("headclick2", c=c, p=p, event=event) 

771 else: 

772 auto_edit = None 

773 g.trace('*** no p') 

774 # 2011/05/27: click here is like ctrl-g. 

775 c.k.keyboardQuit(setFocus=False) 

776 c.treeWantsFocus() # 2011/05/08: Focus must stay in the tree! 

777 c.outerUpdate() 

778 # 2011/06/01: A second *single* click on a selected node 

779 # enters editing state. 

780 if auto_edit and self.auto_edit: 

781 e, wrapper = self.createTreeEditorForItem(item) 

782 finally: 

783 self.busy = False 

784 #@+node:ekr.20110605121601.17895: *4* qtree.onItemCollapsed 

785 def onItemCollapsed(self, item): 

786 

787 if self.busy: 

788 return 

789 c = self.c 

790 p = self.item2position(item) 

791 if not p: 

792 self.error('no p') 

793 return 

794 # Do **not** set lockouts here. 

795 # Only methods that actually generate events should set lockouts. 

796 if p.isExpanded(): 

797 p.contract() 

798 c.redraw_after_contract(p) 

799 self.select(p) 

800 c.outerUpdate() 

801 #@+node:ekr.20110605121601.17897: *4* qtree.onItemDoubleClicked 

802 def onItemDoubleClicked(self, item, col): 

803 """Handle a double click in a BaseNativeTree widget item.""" 

804 if self.busy: # Required. 

805 return 

806 c = self.c 

807 try: 

808 self.busy = True 

809 e, wrapper = self.createTreeEditorForItem(item) 

810 if not e: 

811 g.trace('*** no e') 

812 p = self.item2position(item) 

813 # 2011/07/28: End the lockout here, not at the end. 

814 finally: 

815 self.busy = False 

816 if not p: 

817 self.error('no p') 

818 return 

819 # 2014/02/21: generate headddlick1/2 instead of icondclick1/2. 

820 if g.doHook("headdclick1", c=c, p=p, event=None) is None: 

821 c.frame.tree.OnIconDoubleClick(p) # Call the base class method. 

822 g.doHook("headclick2", c=c, p=p, event=None) 

823 c.outerUpdate() 

824 #@+node:ekr.20110605121601.17898: *4* qtree.onItemExpanded 

825 def onItemExpanded(self, item): 

826 """Handle and tree-expansion event.""" 

827 if self.busy: # Required 

828 return 

829 c = self.c 

830 p = self.item2position(item) 

831 if not p: 

832 self.error('no p') 

833 return 

834 # Do **not** set lockouts here. 

835 # Only methods that actually generate events should set lockouts. 

836 if not p.isExpanded(): 

837 p.expand() 

838 c.redraw_after_expand(p) 

839 self.select(p) 

840 c.outerUpdate() 

841 #@+node:ekr.20110605121601.17899: *4* qtree.onTreeSelect 

842 def onTreeSelect(self): 

843 """Select the proper position when a tree node is selected.""" 

844 if self.busy: # Required 

845 return 

846 c = self.c 

847 item = self.getCurrentItem() 

848 p = self.item2position(item) 

849 if not p: 

850 self.error(f"no p for item: {item}") 

851 return 

852 # Do **not** set lockouts here. 

853 # Only methods that actually generate events should set lockouts. 

854 self.select(p) 

855 # This is a call to LeoTree.select(!!) 

856 c.outerUpdate() 

857 #@+node:ekr.20110605121601.17944: *3* qtree.Focus 

858 def getFocus(self): 

859 return g.app.gui.get_focus(self.c) # Bug fix: 2009/6/30 

860 

861 findFocus = getFocus 

862 

863 def setFocus(self): 

864 g.app.gui.set_focus(self.c, self.treeWidget) 

865 #@+node:ekr.20110605121601.18409: *3* qtree.Icons 

866 #@+node:ekr.20110605121601.18410: *4* qtree.drawIcon 

867 def drawIcon(self, p): 

868 """Redraw the icon at p.""" 

869 return self.updateIcon(p) 

870 # the following code is wrong. It constructs a new item 

871 # and assignes the icon to it. However this item is never 

872 # added to the treeWidget so it is soon garbage collected 

873 # w = self.treeWidget 

874 # itemOrTree = self.position2item(p) or w 

875 # item = QtWidgets.QTreeWidgetItem(itemOrTree) 

876 # icon = self.getIcon(p) 

877 # self.setItemIcon(item, icon) 

878 #@+node:ekr.20110605121601.18411: *4* qtree.getIcon & helper 

879 def getIcon(self, p): 

880 """Return the proper icon for position p.""" 

881 if self.use_declutter: 

882 item = self.position2item(p) 

883 return item and self.declutter_node(self.c, p, item) 

884 p.v.iconVal = iv = p.v.computeIcon() 

885 return self.getCompositeIconImage(p, iv) 

886 

887 

888 #@+node:vitalije.20200329153148.1: *5* qtree.icon_filenames_for_node 

889 def icon_filenames_for_node(self, p, val): 

890 """Prepares and returns a list of icon filenames 

891 related to this node. 

892 """ 

893 nicon = f'box{val:02d}.png' 

894 fnames = self.nodeIconsDict.get(p.gnx) 

895 if not fnames: 

896 icons = self.c.editCommands.getIconList(p) 

897 fnames = [x['file'] for x in icons if x['where'] == 'beforeIcon'] 

898 fnames.append(nicon) 

899 fnames.extend(x['file'] for x in icons if x['where'] == 'beforeHeadline') 

900 self.nodeIconsDict[p.gnx] = fnames 

901 pat = re.compile(r'^box\d\d\.png$') 

902 loaded_images = self.loaded_images 

903 for i, f in enumerate(fnames): 

904 if pat.match(f): 

905 fnames[i] = nicon 

906 self.nodeIconsDict[p.gnx] = fnames 

907 f = nicon 

908 if f not in loaded_images: 

909 loaded_images[f] = g.app.gui.getImageImage(f) 

910 return fnames 

911 #@+node:vitalije.20200329153154.1: *5* qtree.make_composite_icon 

912 def make_composite_icon(self, images): 

913 hsep = self.c.config.getInt('tree-icon-separation') or 0 

914 images = [x for x in images if x] 

915 height = max([i.height() for i in images]) 

916 images = [i.scaledToHeight(height) for i in images] 

917 width = sum([i.width() for i in images]) + hsep * (len(images) - 1) 

918 pix = QtGui.QImage(width, height, Format.Format_ARGB32_Premultiplied) 

919 pix.fill(QtGui.QColor(0, 0, 0, 0).rgba()) # transparent fill, rgbA 

920 # .rgba() call required for Qt4.7, later versions work with straight color 

921 painter = QtGui.QPainter() 

922 if not painter.begin(pix): 

923 print("Failed to init. painter for icon") 

924 # don't return, the code still makes an icon for the cache 

925 # which stops this being called again and again 

926 x = 0 

927 for i in images: 

928 painter.drawPixmap(x, 0, i) 

929 x += i.width() + hsep 

930 painter.end() 

931 return QtGui.QIcon(QtGui.QPixmap.fromImage(pix)) 

932 #@+node:ekr.20110605121601.18412: *5* qtree.getCompositeIconImage 

933 def getCompositeIconImage(self, p, val): 

934 """Get the icon at position p.""" 

935 fnames = self.icon_filenames_for_node(p, val) 

936 h = ':'.join(fnames) 

937 icon = g.app.gui.iconimages.get(h) 

938 loaded_images = self.loaded_images 

939 images = list(map(loaded_images.get, fnames)) 

940 if not icon: 

941 icon = self.make_composite_icon(images) 

942 g.app.gui.iconimages[h] = icon 

943 return icon 

944 #@+node:ekr.20110605121601.17950: *4* qtree.setItemIcon 

945 def setItemIcon(self, item, icon): 

946 

947 valid = item and self.isValidItem(item) 

948 if icon and valid: 

949 # Important: do not set lockouts here. 

950 # This will generate changed events, 

951 # but there is no itemChanged event handler. 

952 item.setIcon(0, icon) 

953 

954 #@+node:ekr.20110605121601.17951: *4* qtree.updateIcon & updateAllIcons 

955 def updateIcon(self, p): 

956 """Update p's icon.""" 

957 if not p: 

958 return 

959 val = p.v.computeIcon() 

960 if p.v.iconVal != val: 

961 self.nodeIconsDict.pop(p.gnx, None) 

962 self.getIcon(p) # sets p.v.iconVal 

963 

964 def updateAllIcons(self, p): 

965 if not p: 

966 return 

967 self.nodeIconsDict.pop(p.gnx, None) 

968 icon = self.getIcon(p) # sets p.v.iconVal 

969 # Update all cloned items. 

970 items = self.vnode2items(p.v) 

971 for item in items: 

972 self.setItemIcon(item, icon) 

973 #@+node:ekr.20110605121601.17952: *4* qtree.updateVisibleIcons 

974 def updateVisibleIcons(self, p): 

975 """Update the icon for p and the icons 

976 for all visible descendants of p.""" 

977 self.updateAllIcons(p) 

978 if p.hasChildren() and p.isExpanded(): 

979 for child in p.children(): 

980 self.updateVisibleIcons(child) 

981 #@+node:ekr.20110605121601.18414: *3* qtree.Items 

982 #@+node:ekr.20110605121601.17943: *4* qtree.item dict getters 

983 def itemHash(self, item): 

984 return f"{repr(item)} at {str(id(item))}" 

985 

986 def item2position(self, item): 

987 itemHash = self.itemHash(item) 

988 p = self.item2positionDict.get(itemHash) # was item 

989 return p 

990 

991 def item2vnode(self, item): 

992 itemHash = self.itemHash(item) 

993 return self.item2vnodeDict.get(itemHash) # was item 

994 

995 def position2item(self, p): 

996 item = self.position2itemDict.get(p.key()) 

997 return item 

998 

999 def vnode2items(self, v): 

1000 return self.vnode2itemsDict.get(v, []) 

1001 

1002 def isValidItem(self, item): 

1003 itemHash = self.itemHash(item) 

1004 return itemHash in self.item2vnodeDict # was item. 

1005 #@+node:ekr.20110605121601.18415: *4* qtree.childIndexOfItem 

1006 def childIndexOfItem(self, item): 

1007 parent = item and item.parent() 

1008 if parent: 

1009 n = parent.indexOfChild(item) 

1010 else: 

1011 w = self.treeWidget 

1012 n = w.indexOfTopLevelItem(item) 

1013 return n 

1014 #@+node:ekr.20110605121601.18416: *4* qtree.childItems 

1015 def childItems(self, parent_item): 

1016 """ 

1017 Return the list of child items of the parent item, 

1018 or the top-level items if parent_item is None. 

1019 """ 

1020 if parent_item: 

1021 n = parent_item.childCount() 

1022 items = [parent_item.child(z) for z in range(n)] 

1023 else: 

1024 w = self.treeWidget 

1025 n = w.topLevelItemCount() 

1026 items = [w.topLevelItem(z) for z in range(n)] 

1027 return items 

1028 #@+node:ekr.20110605121601.18418: *4* qtree.connectEditorWidget & callback 

1029 def connectEditorWidget(self, e, item): 

1030 """ 

1031 Connect QLineEdit e to QTreeItem item. 

1032 

1033 Also callback for when the editor ends. 

1034 

1035 New in Leo 6.4: The callback handles all updates w/o calling onHeadChanged. 

1036 """ 

1037 c, p, u = self.c, self.c.p, self.c.undoer 

1038 #@+others # define the callback. 

1039 #@+node:ekr.20201109043641.1: *5* function: editingFinished_callback 

1040 def editingFinished_callback(): 

1041 """Called when Qt emits the editingFinished signal.""" 

1042 s = e.text() 

1043 i = s.find('\n') 

1044 # Truncate to one line. 

1045 if i > -1: 

1046 s = s[:i] 

1047 # #1310: update the tooltip. 

1048 if p.h != s: 

1049 # Update p.h and handle undo. 

1050 item.setToolTip(0, s) 

1051 undoData = u.beforeChangeHeadline(p) 

1052 p.v.setHeadString(s) # Set v.h *after* calling the undoer's before method. 

1053 if not c.changed: 

1054 c.setChanged() 

1055 # We must recolor the body because 

1056 # the headline may contain directives. 

1057 c.frame.body.recolor(p) 

1058 p.setDirty() 

1059 u.afterChangeHeadline(p, 'Edit Headline', undoData) 

1060 self.redraw_after_head_changed() 

1061 c.outerUpdate() 

1062 #@-others 

1063 if e: 

1064 # Hook up the widget. 

1065 wrapper = self.getWrapper(e, item) 

1066 e.editingFinished.connect(editingFinished_callback) 

1067 return wrapper # 2011/02/12 

1068 g.trace('can not happen: no e') 

1069 return None 

1070 #@+node:ekr.20110605121601.18419: *4* qtree.contractItem & expandItem 

1071 def contractItem(self, item): 

1072 self.treeWidget.collapseItem(item) 

1073 

1074 def expandItem(self, item): 

1075 self.treeWidget.expandItem(item) 

1076 #@+node:ekr.20110605121601.18420: *4* qtree.createTreeEditorForItem 

1077 def createTreeEditorForItem(self, item): 

1078 

1079 c = self.c 

1080 w = self.treeWidget 

1081 w.setCurrentItem(item) # Must do this first. 

1082 if self.use_declutter: 

1083 item.setText(0, item._real_text) 

1084 w.editItem(item) 

1085 e = w.itemWidget(item, 0) # e is a QLineEdit 

1086 e.setObjectName('headline') 

1087 wrapper = self.connectEditorWidget(e, item) 

1088 self.sizeTreeEditor(c, e) 

1089 return e, wrapper 

1090 #@+node:ekr.20110605121601.18421: *4* qtree.createTreeItem 

1091 def createTreeItem(self, p, parent_item): 

1092 

1093 w = self.treeWidget 

1094 itemOrTree = parent_item or w 

1095 item = QtWidgets.QTreeWidgetItem(itemOrTree) 

1096 if isQt6: 

1097 item.setFlags(item.flags() | ItemFlag.ItemIsEditable) 

1098 ChildIndicatorPolicy = QtWidgets.QTreeWidgetItem.ChildIndicatorPolicy 

1099 item.setChildIndicatorPolicy(ChildIndicatorPolicy.DontShowIndicatorWhenChildless) # pylint: disable=no-member 

1100 else: 

1101 item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable | item.DontShowIndicatorWhenChildless) 

1102 try: 

1103 g.visit_tree_item(self.c, p, item) 

1104 except leoPlugins.TryNext: 

1105 pass 

1106 return item 

1107 #@+node:ekr.20110605121601.18423: *4* qtree.getCurrentItem 

1108 def getCurrentItem(self): 

1109 w = self.treeWidget 

1110 return w.currentItem() 

1111 #@+node:ekr.20110605121601.18424: *4* qtree.getItemText 

1112 def getItemText(self, item): 

1113 """Return the text of the item.""" 

1114 return item.text(0) if item else '<no item>' 

1115 #@+node:ekr.20110605121601.18425: *4* qtree.getParentItem 

1116 def getParentItem(self, item): 

1117 return item and item.parent() 

1118 #@+node:ekr.20110605121601.18426: *4* qtree.getSelectedItems 

1119 def getSelectedItems(self): 

1120 w = self.treeWidget 

1121 return w.selectedItems() 

1122 #@+node:ekr.20110605121601.18427: *4* qtree.getTreeEditorForItem 

1123 def getTreeEditorForItem(self, item): 

1124 """Return the edit widget if it exists. 

1125 Do *not* create one if it does not exist. 

1126 """ 

1127 w = self.treeWidget 

1128 e = w.itemWidget(item, 0) 

1129 return e 

1130 #@+node:ekr.20110605121601.18428: *4* qtree.getWrapper 

1131 def getWrapper(self, e, item): 

1132 """Return headlineWrapper that wraps e (a QLineEdit).""" 

1133 c = self.c 

1134 if e: 

1135 wrapper = self.editWidgetsDict.get(e) 

1136 if wrapper: 

1137 pass 

1138 else: 

1139 if item: 

1140 # 2011/02/12: item can be None. 

1141 wrapper = self.headlineWrapper(c, item, name='head', widget=e) 

1142 self.editWidgetsDict[e] = wrapper 

1143 return wrapper 

1144 g.trace('no e') 

1145 return None 

1146 #@+node:ekr.20110605121601.18429: *4* qtree.nthChildItem 

1147 def nthChildItem(self, n, parent_item): 

1148 children = self.childItems(parent_item) 

1149 if n < len(children): 

1150 item = children[n] 

1151 else: 

1152 # This is **not* an error. 

1153 # It simply means that we need to redraw the tree. 

1154 item = None 

1155 return item 

1156 #@+node:ekr.20110605121601.18430: *4* qtree.scrollToItem 

1157 def scrollToItem(self, item): 

1158 """ 

1159 Scroll the tree widget so that item is visible. 

1160 Leo's core no longer calls this method. 

1161 """ 

1162 w = self.treeWidget 

1163 hPos, vPos = self.getScroll() 

1164 w.scrollToItem(item, w.EnsureVisible) 

1165 # Fix #265: Erratic scrolling bug. 

1166 # w.PositionAtCenter causes unwanted scrolling. 

1167 self.setHScroll(0) 

1168 # Necessary 

1169 #@+node:ekr.20110605121601.18431: *4* qtree.setCurrentItemHelper 

1170 def setCurrentItemHelper(self, item): 

1171 w = self.treeWidget 

1172 w.setCurrentItem(item) 

1173 #@+node:ekr.20110605121601.18432: *4* qtree.setItemText 

1174 def setItemText(self, item, s): 

1175 if item: 

1176 item.setText(0, s) 

1177 if self.use_declutter: 

1178 item._real_text = s 

1179 #@+node:tbrown.20160406221505.1: *4* qtree.sizeTreeEditor 

1180 @staticmethod 

1181 def sizeTreeEditor(c, editor): 

1182 """Size a QLineEdit in a tree headline so scrolling occurs""" 

1183 # space available in tree widget 

1184 space = c.frame.tree.treeWidget.size().width() 

1185 # left hand edge of editor within tree widget 

1186 used = editor.geometry().x() + 4 # + 4 for edit cursor 

1187 # limit width to available space 

1188 editor.resize(space - used, editor.size().height()) 

1189 #@+node:ekr.20110605121601.18433: *3* qtree.Scroll bars 

1190 #@+node:ekr.20110605121601.18434: *4* qtree.getSCroll 

1191 def getScroll(self): 

1192 """Return the hPos,vPos for the tree's scrollbars.""" 

1193 w = self.treeWidget 

1194 hScroll = w.horizontalScrollBar() 

1195 vScroll = w.verticalScrollBar() 

1196 hPos = hScroll.sliderPosition() 

1197 vPos = vScroll.sliderPosition() 

1198 return hPos, vPos 

1199 #@+node:btheado.20111110215920.7164: *4* qtree.scrollDelegate 

1200 def scrollDelegate(self, kind): 

1201 """ 

1202 Scroll a QTreeWidget up or down or right or left. 

1203 kind is in ('down-line','down-page','up-line','up-page', 'right', 'left') 

1204 """ 

1205 c = self.c 

1206 w = self.treeWidget 

1207 if kind in ('left', 'right'): 

1208 hScroll = w.horizontalScrollBar() 

1209 if kind == 'right': 

1210 delta = hScroll.pageStep() 

1211 else: 

1212 delta = -hScroll.pageStep() 

1213 hScroll.setValue(hScroll.value() + delta) 

1214 else: 

1215 vScroll = w.verticalScrollBar() 

1216 h = w.size().height() 

1217 lineSpacing = w.fontMetrics().lineSpacing() 

1218 n = h / lineSpacing 

1219 if kind == 'down-half-page': 

1220 delta = n / 2 

1221 elif kind == 'down-line': 

1222 delta = 1 

1223 elif kind == 'down-page': 

1224 delta = n 

1225 elif kind == 'up-half-page': 

1226 delta = -n / 2 

1227 elif kind == 'up-line': 

1228 delta = -1 

1229 elif kind == 'up-page': 

1230 delta = -n 

1231 else: 

1232 delta = 0 

1233 g.trace('bad kind:', kind) 

1234 val = vScroll.value() 

1235 vScroll.setValue(val + delta) 

1236 c.treeWantsFocus() 

1237 #@+node:ekr.20110605121601.18435: *4* qtree.setH/VScroll 

1238 def setHScroll(self, hPos): 

1239 

1240 w = self.treeWidget 

1241 hScroll = w.horizontalScrollBar() 

1242 hScroll.setValue(hPos) 

1243 

1244 def setVScroll(self, vPos): 

1245 

1246 w = self.treeWidget 

1247 vScroll = w.verticalScrollBar() 

1248 vScroll.setValue(vPos) 

1249 #@+node:ekr.20110605121601.17905: *3* qtree.Selecting & editing 

1250 #@+node:ekr.20110605121601.17908: *4* qtree.edit_widget 

1251 def edit_widget(self, p): 

1252 """Returns the edit widget for position p.""" 

1253 item = self.position2item(p) 

1254 if item: 

1255 e = self.getTreeEditorForItem(item) 

1256 if e: 

1257 # Create a wrapper widget for Leo's core. 

1258 w = self.getWrapper(e, item) 

1259 return w 

1260 # This is not an error 

1261 # But warning: calling this method twice might not work! 

1262 return None 

1263 return None 

1264 #@+node:ekr.20110605121601.17909: *4* qtree.editLabel and helper 

1265 def editLabel(self, p, selectAll=False, selection=None): 

1266 """Start editing p's headline.""" 

1267 if self.busy: 

1268 return None 

1269 c = self.c 

1270 c.outerUpdate() 

1271 # Do any scheduled redraw. 

1272 # This won't do anything in the new redraw scheme. 

1273 item = self.position2item(p) 

1274 if item: 

1275 if self.use_declutter: 

1276 item.setText(0, item._real_text) 

1277 e, wrapper = self.editLabelHelper(item, selectAll, selection) 

1278 else: 

1279 e, wrapper = None, None 

1280 self.error(f"no item for {p}") 

1281 if e: 

1282 self.sizeTreeEditor(c, e) 

1283 # A nice hack: just set the focus request. 

1284 c.requestedFocusWidget = e 

1285 return e, wrapper 

1286 #@+node:ekr.20110605121601.18422: *5* qtree.editLabelHelper 

1287 def editLabelHelper(self, item, selectAll=False, selection=None): 

1288 """Helper for qtree.editLabel.""" 

1289 c, vc = self.c, self.c.vimCommands 

1290 w = self.treeWidget 

1291 w.setCurrentItem(item) 

1292 # Must do this first. 

1293 # This generates a call to onTreeSelect. 

1294 w.editItem(item) 

1295 # Generates focus-in event that tree doesn't report. 

1296 e = w.itemWidget(item, 0) # A QLineEdit. 

1297 s = e.text() 

1298 if s == 'newHeadline': 

1299 selectAll = True 

1300 if selection: 

1301 # pylint: disable=unpacking-non-sequence 

1302 # Fix bug https://groups.google.com/d/msg/leo-editor/RAzVPihqmkI/-tgTQw0-LtwJ 

1303 # Note: negative lengths are allowed. 

1304 i, j, ins = selection 

1305 if ins is None: 

1306 start, n = i, abs(i - j) 

1307 # This case doesn't happen for searches. 

1308 elif ins == j: 

1309 start, n = i, j - i 

1310 else: 

1311 start = start, n = j, i - j 

1312 elif selectAll: 

1313 start, n, ins = 0, len(s), len(s) 

1314 else: 

1315 start, n, ins = len(s), 0, len(s) 

1316 e.setObjectName('headline') 

1317 e.setSelection(start, n) 

1318 # e.setCursorPosition(ins) # Does not work. 

1319 e.setFocus() 

1320 wrapper = self.connectEditorWidget(e, item) # Hook up the widget. 

1321 if vc and c.vim_mode: # and selectAll 

1322 # For now, *always* enter insert mode. 

1323 if vc.is_text_wrapper(wrapper): 

1324 vc.begin_insert_mode(w=wrapper) 

1325 else: 

1326 g.trace('not a text widget!', wrapper) 

1327 return e, wrapper 

1328 #@+node:ekr.20110605121601.17911: *4* qtree.endEditLabel 

1329 def endEditLabel(self): 

1330 """ 

1331 Override LeoTree.endEditLabel. 

1332 

1333 Just end editing of the presently-selected QLineEdit! 

1334 This will trigger the editingFinished_callback defined in createEditorForItem. 

1335 """ 

1336 item = self.getCurrentItem() 

1337 if not item: 

1338 return 

1339 e = self.getTreeEditorForItem(item) 

1340 if not e: 

1341 return 

1342 # Trigger the end-editing event. 

1343 w = self.treeWidget 

1344 w.closeEditor(e, EndEditHint.NoHint) 

1345 w.setCurrentItem(item) 

1346 #@+node:ekr.20110605121601.17915: *4* qtree.getSelectedPositions 

1347 def getSelectedPositions(self): 

1348 items = self.getSelectedItems() 

1349 pl = leoNodes.PosList(self.item2position(it) for it in items) 

1350 return pl 

1351 #@+node:ekr.20110605121601.17914: *4* qtree.setHeadline 

1352 def setHeadline(self, p, s): 

1353 """Force the actual text of the headline widget to p.h.""" 

1354 # This is used by unit tests to force the headline and p into alignment. 

1355 if not p: 

1356 return 

1357 # Don't do this here: the caller should do it. 

1358 # p.setHeadString(s) 

1359 e = self.edit_widget(p) 

1360 if e: 

1361 e.setAllText(s) 

1362 else: 

1363 item = self.position2item(p) 

1364 if item: 

1365 self.setItemText(item, s) 

1366 #@+node:ekr.20110605121601.17913: *4* qtree.setItemForCurrentPosition 

1367 def setItemForCurrentPosition(self): 

1368 """Select the item for c.p""" 

1369 p = self.c.p 

1370 if self.busy: 

1371 return None 

1372 if not p: 

1373 return None 

1374 item = self.position2item(p) 

1375 if not item: 

1376 # This is not necessarily an error. 

1377 # We often attempt to select an item before redrawing it. 

1378 return None 

1379 item2 = self.getCurrentItem() 

1380 if item == item2: 

1381 return item 

1382 try: 

1383 self.busy = True 

1384 self.treeWidget.setCurrentItem(item) 

1385 # This generates gui events, so we must use a lockout. 

1386 finally: 

1387 self.busy = False 

1388 return item 

1389 #@+node:ekr.20190613080606.1: *4* qtree.unselectItem 

1390 def unselectItem(self, p): 

1391 

1392 item = self.position2item(p) 

1393 if item: 

1394 item.setSelected(False) 

1395 #@-others 

1396#@-others 

1397#@@language python 

1398#@@tabwidth -4 

1399#@@pagewidth 80 

1400#@-leo