Merge lp:~terry-n-brown/leo-editor/free_layout into lp:leo-editor/old-trunk
- free_layout
- Merge into trunk
Proposed by
tbnorth
Status: | Merged |
---|---|
Merged at revision: | 3870 |
Proposed branch: | lp:~terry-n-brown/leo-editor/free_layout |
Merge into: | lp:leo-editor/old-trunk |
Diff against target: |
671 lines (+623/-3) 4 files modified
leo/plugins/free_layout.py (+100/-0) leo/plugins/leoPluginsRef.leo (+2/-1) leo/plugins/nested_splitter.py (+509/-0) leo/plugins/qtGui.py (+12/-2) |
To merge this branch: | bzr merge lp:~terry-n-brown/leo-editor/free_layout |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edward K. Ream | Pending | ||
Review via email: mp+50460@code.launchpad.net |
Commit message
Description of the change
I think this can safely be merged into the trunk because the changes to qtGui.py are very minor and it's clear they have no effect unless the plugin is enabled.
To post a comment you must log in.
Revision history for this message
Edward K. Ream (edreamleo) wrote : | # |
Revision history for this message
tbnorth (terry-n-brown) wrote : | # |
On Tue, 22 Feb 2011 17:02:01 -0000
"Edward K. Ream" <email address hidden> wrote:
> Do I need to do anything to approve this? If not, please just merge,
> assuming all unit tests pass.
All pass 2.6/3.1, so this is in the trunk now.
If you enable the free_layout plugin there are context menus on the
panel dividers in the GUI to add panels and move panels around.
Cheers -Terry
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'leo/plugins/free_layout.py' |
2 | --- leo/plugins/free_layout.py 1970-01-01 00:00:00 +0000 |
3 | +++ leo/plugins/free_layout.py 2011-02-19 19:01:52 +0000 |
4 | @@ -0,0 +1,100 @@ |
5 | +#@+leo-ver=5-thin |
6 | +#@+node:tbrown.20110203111907.5519: * @file free_layout.py |
7 | +"""Adds flexible panel layout through context menus on the handles between panels. |
8 | +Requires Qt. |
9 | +""" |
10 | +#@+others |
11 | +#@+node:tbrown.20110203111907.5520: ** declarations |
12 | +__version__ = '0.1' |
13 | +# |
14 | +# 0.1 - initial release - TNB |
15 | + |
16 | +import leo.core.leoGlobals as g |
17 | + |
18 | +g.assertUi('qt') |
19 | + |
20 | +from PyQt4 import QtCore, QtGui, Qt |
21 | + |
22 | +from leo.plugins.nested_splitter import NestedSplitter |
23 | +#@+node:tbrown.20110203111907.5521: ** init |
24 | +def init(): |
25 | + |
26 | + if g.app.gui.guiName() != "qt": |
27 | + return False |
28 | + |
29 | + g.registerHandler('after-create-leo-frame',onCreate) |
30 | + # can't use before-create-leo-frame because Qt dock's not ready |
31 | + g.plugin_signon(__name__) |
32 | + |
33 | + return True |
34 | +#@+node:tbrown.20110203111907.5522: ** onCreate |
35 | +def onCreate(tag, keys): |
36 | + |
37 | + c = keys.get('c') |
38 | + if not c: return |
39 | + |
40 | + NestedSplitter.enabled = True |
41 | + |
42 | + # define menu callbacks here where c is in scope |
43 | + def offer_tabs(menu, splitter, index, button_mode): |
44 | + |
45 | + if not button_mode: |
46 | + return |
47 | + |
48 | + top_splitter = c.frame.top.splitter_2.top() |
49 | + logTabWidget = top_splitter.findChild(QtGui.QWidget, "logTabWidget") |
50 | + |
51 | + for n in range(logTabWidget.count()): |
52 | + |
53 | + act = QtGui.QAction("Add "+logTabWidget.tabText(n), splitter) |
54 | + |
55 | + def wrapper(w=logTabWidget.widget(n), s=splitter, |
56 | + t=logTabWidget.tabText(n)): |
57 | + w.setHidden(False) |
58 | + w._is_from_tab = t |
59 | + s.replace_widget(s.widget(index), w) |
60 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
61 | + menu.addAction(act) |
62 | + |
63 | + def offer_viewrendered(menu, splitter, index, button_mode): |
64 | + |
65 | + if not button_mode: |
66 | + return |
67 | + |
68 | + if hasattr(c, "viewrendered") and c.viewrendered: |
69 | + |
70 | + act = QtGui.QAction("Add viewrendered", splitter) |
71 | + |
72 | + def wrapper(w=c.viewrendered, s=splitter): |
73 | + s.replace_widget(s.widget(index), w) |
74 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
75 | + menu.addAction(act) |
76 | + |
77 | + |
78 | + splitter = c.frame.top.splitter_2.top() |
79 | + |
80 | + # register menu callbacks |
81 | + splitter.register(offer_tabs) |
82 | + splitter.register(offer_viewrendered) |
83 | + |
84 | + # when NestedSplitter disposes of children, it will either close |
85 | + # them, or move them to another designated widget. Here we set |
86 | + # up two designated widgets |
87 | + |
88 | + logTabWidget = splitter.findChild(QtGui.QWidget, "logTabWidget") |
89 | + splitter.root.holders['_is_from_tab'] = logTabWidget |
90 | + splitter.root.holders['_is_permanent'] = splitter |
91 | + |
92 | + # allow body and tree widgets to be "removed" to tabs on the log tab panel |
93 | + bodyWidget = splitter.findChild(QtGui.QFrame, "bodyFrame") |
94 | + bodyWidget._is_from_tab = "Body" |
95 | + treeWidget = splitter.findChild(QtGui.QFrame, "outlineFrame") |
96 | + treeWidget._is_from_tab = "Tree" |
97 | + # also the other tabs will have _is_from_tab set on them by the |
98 | + # offer_tabs menu callback above |
99 | + |
100 | + # if the log tab panel is removed, move it back to the top splitter |
101 | + logWidget = splitter.findChild(QtGui.QFrame, "logFrame") |
102 | + logWidget._is_permanent = True |
103 | +#@-others |
104 | +#@-leo |
105 | |
106 | === modified file 'leo/plugins/leoPluginsRef.leo' |
107 | --- leo/plugins/leoPluginsRef.leo 2011-02-13 18:11:37 +0000 |
108 | +++ leo/plugins/leoPluginsRef.leo 2011-02-19 19:01:52 +0000 |
109 | @@ -76,8 +76,9 @@ |
110 | expanded="tbrown.20090119215428.9,tbrown.20090119215428.11,tbrown.20090119215428.19,tbrown.20090119215428.30,tbrown.20090119215428.40,"><vh>@file todo.py</vh></v> |
111 | <v t="tbrown.20100318101414.5990" |
112 | expanded="tbrown.20100318101414.5997,"><vh>@file viewrendered.py</vh></v> |
113 | -<v t="tbrown.20090206153748.1" a="E" |
114 | +<v t="tbrown.20090206153748.1" |
115 | expanded="bob.20110119123023.7395,bob.20110119123023.7400,bob.20110121161547.3424,bob.20110119123023.7408,bob.20110120111825.3352,bob.20110121113659.3412,bob.20110121113659.3414,bob.20110121113659.3413,"><vh>@file graphcanvas.py</vh></v> |
116 | +<v t="tbrown.20110203111907.5519" a="E"><vh>@file free_layout.py</vh></v> |
117 | </v> |
118 | <v t="edream.110203113231.667"><vh>Commands & directives</vh> |
119 | <v t="edream.110203113231.741"><vh>@file add_directives.py</vh></v> |
120 | |
121 | === added file 'leo/plugins/nested_splitter.py' |
122 | --- leo/plugins/nested_splitter.py 1970-01-01 00:00:00 +0000 |
123 | +++ leo/plugins/nested_splitter.py 2011-02-19 19:01:52 +0000 |
124 | @@ -0,0 +1,509 @@ |
125 | +import sys |
126 | +from inspect import isclass |
127 | +from PyQt4 import QtGui, QtCore, Qt |
128 | +from PyQt4.QtCore import Qt as QtConst |
129 | +class DemoWidget(QtGui.QWidget): |
130 | + |
131 | + count = 0 |
132 | + |
133 | + |
134 | + def __init__(self, parent=None, color=None): |
135 | + |
136 | + QtGui.QWidget.__init__(self, parent) |
137 | + |
138 | + self.setLayout(QtGui.QVBoxLayout()) |
139 | + self.layout().setContentsMargins(QtCore.QMargins(0,0,0,0)) |
140 | + self.layout().setSpacing(0) |
141 | + |
142 | + text = QtGui.QTextEdit() |
143 | + self.layout().addWidget(text) |
144 | + DemoWidget.count += 1 |
145 | + text.setPlainText("#%d" % DemoWidget.count) |
146 | + |
147 | + button_layout = QtGui.QHBoxLayout() |
148 | + button_layout.setContentsMargins(QtCore.QMargins(5,5,5,5)) |
149 | + self.layout().addLayout(button_layout) |
150 | + |
151 | + |
152 | + button_layout.addWidget(QtGui.QPushButton("Go")) |
153 | + button_layout.addWidget(QtGui.QPushButton("Stop")) |
154 | + |
155 | + if color: |
156 | + self.setStyleSheet("background-color: %s;"%color) |
157 | + |
158 | +class NestedSplitterChoice(QtGui.QWidget): |
159 | + |
160 | + def __init__(self, parent=None): |
161 | + |
162 | + QtGui.QWidget.__init__(self, parent) |
163 | + |
164 | + self.setLayout(QtGui.QVBoxLayout()) |
165 | + |
166 | + button = QtGui.QPushButton("Action", parent=self) |
167 | + self.layout().addWidget(button) |
168 | + |
169 | + button.setContextMenuPolicy(QtConst.CustomContextMenu) |
170 | + button.connect(button, |
171 | + Qt.SIGNAL('customContextMenuRequested(QPoint)'), |
172 | + lambda pnt: self.parent().choice_menu(self, pnt)) |
173 | + button.connect(button, |
174 | + Qt.SIGNAL('clicked()'), |
175 | + lambda: self.parent().choice_menu(self, button.pos())) |
176 | + |
177 | +class NestedSplitterHandle(QtGui.QSplitterHandle): |
178 | + |
179 | + def __init__(self, owner): |
180 | + QtGui.QSplitterHandle.__init__(self, owner.orientation(), owner) |
181 | + |
182 | + self.setStyleSheet("background-color: green;") |
183 | + |
184 | + self.setContextMenuPolicy(QtConst.CustomContextMenu) |
185 | + self.connect(self, |
186 | + Qt.SIGNAL('customContextMenuRequested(QPoint)'), |
187 | + self.splitter_menu) |
188 | + |
189 | + def splitter_menu(self, pos): |
190 | + |
191 | + splitter = self.splitter() |
192 | + |
193 | + if not splitter.enabled: |
194 | + return |
195 | + |
196 | + splitter = self.splitter() |
197 | + index = splitter.indexOf(self) |
198 | + |
199 | + widget, neighbour, count = splitter.handle_context(index) |
200 | + |
201 | + lr = 'left', 'right' |
202 | + ab = 'above', 'below' |
203 | + split_dir = 'vertically' |
204 | + if self.orientation() == QtConst.Vertical: |
205 | + lr, ab = ab, lr |
206 | + split_dir = 'horizontally' |
207 | + |
208 | + color = '#729fcf', '#f57900' |
209 | + sheet = [] |
210 | + for i in 0,1: |
211 | + sheet.append(widget[i].styleSheet()) |
212 | + widget[i].setStyleSheet(sheet[-1]+"\nborder: 2px solid %s;"%color[i]) |
213 | + |
214 | + menu = QtGui.QMenu() |
215 | + |
216 | + # insert |
217 | + act = QtGui.QAction("Insert", self) |
218 | + act.setObjectName('insert') |
219 | + act.connect(act, Qt.SIGNAL('triggered()'), |
220 | + lambda: splitter.insert(index)) |
221 | + menu.addAction(act) |
222 | + |
223 | + # swap |
224 | + act = QtGui.QAction("Swap %d %s %d %s" % (count[0], lr[0], count[1], lr[1]), self) |
225 | + act.setObjectName('swap') |
226 | + act.connect(act, Qt.SIGNAL('triggered()'), |
227 | + lambda: splitter.swap(index)) |
228 | + menu.addAction(act) |
229 | + |
230 | + # rotate |
231 | + act = QtGui.QAction("Rotate all", self) |
232 | + act.setObjectName('rotate') |
233 | + act.connect(act, Qt.SIGNAL('triggered()'), splitter.rotate) |
234 | + menu.addAction(act) |
235 | + |
236 | + # remove, +0/-1 reversed, we need to test the one that remains |
237 | + |
238 | + # first see if a parent has more than two splits (we could be a sole |
239 | + # surviving child) |
240 | + max_parent_splits = 0 |
241 | + up = splitter.parent() |
242 | + while isinstance(up, NestedSplitter): |
243 | + max_parent_splits = max(max_parent_splits, up.count()) |
244 | + up = up.parent() |
245 | + if max_parent_splits >= 2: |
246 | + break # two is enough |
247 | + |
248 | + for i in 0,1: |
249 | + |
250 | + keep = splitter.widget(index) |
251 | + cull = splitter.widget(index-1) |
252 | + if (max_parent_splits >= 2 or # more splits upstream |
253 | + splitter.count() > 2 or # 3+ splits here, or 2+ downstream |
254 | + neighbour[not i] and neighbour[not i].max_count() >= 2): |
255 | + act = QtGui.QAction("Remove %d %s"%(count[i], lr[i]), self) |
256 | + act.setObjectName('remove %d'%i) |
257 | + def wrapper(i=i): splitter.remove(index, i) |
258 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
259 | + menu.addAction(act) |
260 | + |
261 | + # only offer split if not already split |
262 | + for i in 0,1: |
263 | + if (not neighbour[i] or neighbour[i].count() == 1): |
264 | + act = QtGui.QAction("Split %s %s"%(lr[i], split_dir), self) |
265 | + act.setObjectName('split %d'%i) |
266 | + def wrapper(i=i): splitter.split(index, i) |
267 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
268 | + menu.addAction(act) |
269 | + |
270 | + # mark |
271 | + for i in 0,1: |
272 | + act = QtGui.QAction("Mark %d %s"%(count[i], lr[i]), self) |
273 | + act.setObjectName('mark %d'%i) |
274 | + def wrapper(i=i): splitter.mark(index, i) |
275 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
276 | + menu.addAction(act) |
277 | + |
278 | + # swap with mark |
279 | + if splitter.root.marked: |
280 | + for i in 0,1: |
281 | + if not splitter.invalid_swap(widget[i], splitter.root.marked[2]): |
282 | + act = QtGui.QAction("Swap %d %s with marked"%(count[i], lr[i]), self) |
283 | + act.setObjectName('swap mark %d'%i) |
284 | + def wrapper(i=i): splitter.swap_with_marked(index, i) |
285 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
286 | + menu.addAction(act) |
287 | + |
288 | + # add |
289 | + for i in 0,1: |
290 | + if (not isinstance(splitter.parent(), NestedSplitter) or |
291 | + splitter.parent().indexOf(splitter) == |
292 | + [0,splitter.parent().count()-1][i]): |
293 | + act = QtGui.QAction("Add %s"%ab[i], self) |
294 | + act.setObjectName('add %d'%i) |
295 | + def wrapper(i=i): splitter.add(i) |
296 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
297 | + menu.addAction(act) |
298 | + |
299 | + for cb in splitter.root.callbacks: |
300 | + cb(menu, splitter, index, button_mode=False) |
301 | + |
302 | + menu.exec_(self.mapToGlobal(pos)) |
303 | + |
304 | + for i in 0,1: |
305 | + widget[i].setStyleSheet(sheet[i]) |
306 | + |
307 | +class NestedSplitter(QtGui.QSplitter): |
308 | + |
309 | + enabled = True |
310 | + # allow special behavior to be turned of at import stage |
311 | + # useful if other code must run to set up callbacks, that |
312 | + # other code can re-enable |
313 | + |
314 | + other_orientation = { |
315 | + QtConst.Vertical: QtConst.Horizontal, |
316 | + QtConst.Horizontal: QtConst.Vertical |
317 | + } |
318 | + |
319 | + |
320 | + def __init__(self, parent=None, orientation=QtConst.Horizontal, root=None): |
321 | + QtGui.QSplitter.__init__(self, parent=parent, orientation=orientation) |
322 | + |
323 | + if not root: |
324 | + root = self.top() |
325 | + if root == self: |
326 | + root.marked = None |
327 | + root.callbacks = [] |
328 | + root.holders = {} |
329 | + self.root = root |
330 | + def add(self, side): |
331 | + |
332 | + orientation = self.other_orientation[self.orientation()] |
333 | + |
334 | + if isinstance(self.parent(), NestedSplitter): |
335 | + # don't add new splitter if not needed, i.e. we're the |
336 | + # only child of a previosly more populated splitter |
337 | + |
338 | + self.parent().insertWidget( |
339 | + self.parent().indexOf(self) + side - 1 + 1, |
340 | + NestedSplitterChoice(self)) |
341 | + |
342 | + elif self.parent().layout(): |
343 | + |
344 | + new = NestedSplitter(None, orientation=orientation, |
345 | + root=self.root) |
346 | + # parent set by insertWidget() below |
347 | + |
348 | + old = self |
349 | + |
350 | + pos = self.parent().layout().indexOf(old) |
351 | + |
352 | + self.parent().layout().insertWidget(pos, new) |
353 | + |
354 | + new.addWidget(old) |
355 | + new.insertWidget(side, NestedSplitterChoice(new)) |
356 | + |
357 | + else: |
358 | + # fail - parent is not NestedSplitter and has no layout |
359 | + pass |
360 | + def choice_menu(self, button, pos): |
361 | + |
362 | + menu = QtGui.QMenu() |
363 | + |
364 | + if self.root.marked and not self.invalid_swap(button, self.root.marked[3]): |
365 | + an_item = True |
366 | + act = QtGui.QAction("Move marked here", self) |
367 | + act.connect(act, Qt.SIGNAL('triggered()'), |
368 | + lambda: self.replace_widget(button, self.root.marked[3])) |
369 | + menu.addAction(act) |
370 | + |
371 | + for cb in self.root.callbacks: |
372 | + cb(menu, self, self.indexOf(button), button_mode=True) |
373 | + |
374 | + if menu.isEmpty(): |
375 | + act = QtGui.QAction("Nothing marked, and no options", self) |
376 | + menu.addAction(act) |
377 | + |
378 | + menu.exec_(button.mapToGlobal(pos)) |
379 | + def close_or_keep(self, widget): |
380 | + |
381 | + if widget is None: |
382 | + return |
383 | + |
384 | + for k in self.root.holders: |
385 | + if hasattr(widget, k): |
386 | + holder = self.root.holders[k] |
387 | + if hasattr(holder, "addTab"): |
388 | + holder.addTab(widget, getattr(widget,k)) |
389 | + else: |
390 | + holder.addWidget(widget) |
391 | + break |
392 | + else: |
393 | + widget.close() |
394 | + widget.deleteLater() |
395 | + def contains(self, widget): |
396 | + """check if widget is a descendent of self""" |
397 | + |
398 | + for i in range(self.count()): |
399 | + if widget == self.widget(i): |
400 | + return True |
401 | + if isinstance(self.widget(i), NestedSplitter): |
402 | + if self.widget(i).contains(widget): |
403 | + return True |
404 | + |
405 | + return False |
406 | + |
407 | + def createHandle(self, *args, **kargs): |
408 | + |
409 | + return NestedSplitterHandle(self) |
410 | + |
411 | + def handle_context(self, index): |
412 | + |
413 | + widget = [ |
414 | + self.widget(index-1), |
415 | + self.widget(index), |
416 | + ] |
417 | + |
418 | + neighbour = [ (i if isinstance(i, NestedSplitter) else None) |
419 | + for i in widget ] |
420 | + |
421 | + count = [] |
422 | + for i in 0,1: |
423 | + if neighbour[i]: |
424 | + l = [ii.count() for ii in neighbour[i].self_and_descendants()] |
425 | + n = sum(l) - len(l) + 1 # count leaves, not splitters |
426 | + count.append(n) |
427 | + else: |
428 | + count.append(1) |
429 | + |
430 | + return widget, neighbour, count |
431 | + def insert(self, index): |
432 | + |
433 | + self.insertWidget(index, NestedSplitterChoice(self)) |
434 | + |
435 | + def invalid_swap(self, w0, w1): |
436 | + if w0 == w1: |
437 | + return True |
438 | + if (isinstance(w0, NestedSplitter) and w0.contains(w1) or |
439 | + isinstance(w1, NestedSplitter) and w1.contains(w0)): |
440 | + return True |
441 | + return False |
442 | + |
443 | + def mark(self, index, side): |
444 | + self.root.marked = (self, index, side-1, |
445 | + self.widget(index+side-1)) |
446 | + def max_count(self): |
447 | + """find max widgets in this and child splitters""" |
448 | + |
449 | + return max([i.count() for i in self.self_and_descendants()]) |
450 | + |
451 | + def register(self, cb): |
452 | + |
453 | + self.root.callbacks.append(cb) |
454 | + def remove(self, index, side): |
455 | + |
456 | + widget = self.widget(index+side-1) |
457 | + |
458 | + # clear marked if it's going to be deleted |
459 | + if (self.root.marked and (self.root.marked[3] == widget or |
460 | + isinstance(self.root.marked[3], NestedSplitter) and |
461 | + self.root.marked[3].contains(widget))): |
462 | + self.root.marked = None |
463 | + |
464 | + # send close signal to all children |
465 | + if isinstance(widget, NestedSplitter): |
466 | + |
467 | + count = widget.count() |
468 | + |
469 | + for splitter in widget.self_and_descendants(): |
470 | + for i in range(splitter.count()): |
471 | + self.close_or_keep(splitter.widget(i)) |
472 | + |
473 | + if count <= 0: |
474 | + widget.deleteLater() |
475 | + |
476 | + else: |
477 | + self.close_or_keep(widget) |
478 | + |
479 | + def replace_widget(self, old, new): |
480 | + |
481 | + self.insertWidget(self.indexOf(old), new) |
482 | + old.deleteLater() |
483 | + |
484 | + def rotate(self, descending=False): |
485 | + """Change orientation - current rotates entire hierachy, doing less |
486 | + is visually confusing because you end up with nested splitters with |
487 | + the same orientation - avoiding that would mean doing rotation by |
488 | + inserting out widgets into our ancestors, etc. |
489 | + """ |
490 | + |
491 | + for i in self.top().self_and_descendants(): |
492 | + if i.orientation() == QtConst.Vertical: |
493 | + i.setOrientation(QtConst.Horizontal) |
494 | + else: |
495 | + i.setOrientation(QtConst.Vertical) |
496 | + def self_and_descendants(self): |
497 | + """Yield self and all **NestedSplitter** descendants""" |
498 | + |
499 | + for i in range(self.count()): |
500 | + if isinstance(self.widget(i), NestedSplitter): |
501 | + for w in self.widget(i).self_and_descendants(): |
502 | + yield w |
503 | + yield self |
504 | + def split(self, index, side): |
505 | + |
506 | + old = self.widget(index+side-1) |
507 | + |
508 | + if isinstance(old, NestedSplitter): |
509 | + old.addWidget(NestedSplitterChoice(self)) |
510 | + return |
511 | + |
512 | + orientation = self.other_orientation[self.orientation()] |
513 | + |
514 | + new = NestedSplitter(self, orientation=orientation, root=self.root) |
515 | + self.insertWidget(index+side-1, new) |
516 | + new.addWidget(old) |
517 | + new.addWidget(NestedSplitterChoice(self)) |
518 | + def swap(self, index): |
519 | + |
520 | + self.insertWidget(index-1, self.widget(index)) |
521 | + |
522 | + def swap_with_marked(self, index, side): |
523 | + |
524 | + osplitter, oidx, oside, ow = self.root.marked |
525 | + |
526 | + idx = index+side-1 |
527 | + # convert from handle index to widget index |
528 | + # 1 already subtracted from oside in mark() |
529 | + w = self.widget(idx) |
530 | + |
531 | + if self.invalid_swap(w, ow): |
532 | + # print 'Invalid swap' |
533 | + return |
534 | + |
535 | + self.insertWidget(idx, ow) |
536 | + osplitter.insertWidget(oidx, w) |
537 | + |
538 | + self.root.marked = self, self.indexOf(ow), 0, ow |
539 | + def top(self): |
540 | + """find top widget, which is not necessarily root""" |
541 | + |
542 | + top = self |
543 | + while isinstance(top.parent(), NestedSplitter): |
544 | + top = top.parent() |
545 | + |
546 | + return top |
547 | +if __name__ == "__main__": |
548 | + |
549 | + def demo_nest(splitter): |
550 | + orientation = splitter.other_orientation[splitter.orientation()] |
551 | + x = NestedSplitter(splitter, root=splitter.root, |
552 | + orientation=orientation) |
553 | + s = [x] |
554 | + for i in range(4): |
555 | + orientation = splitter.other_orientation[orientation] |
556 | + ns = [] |
557 | + for j in s: |
558 | + j.addWidget(DemoWidget()) |
559 | + ns.append(NestedSplitter(splitter, orientation=orientation, |
560 | + root=splitter.root)) |
561 | + j.addWidget(ns[-1]) |
562 | + s = ns |
563 | + |
564 | + for i in s: |
565 | + i.addWidget(DemoWidget()) |
566 | + |
567 | + return x |
568 | + |
569 | + def callback(menu, splitter, index, button_mode): |
570 | + |
571 | + if not button_mode: |
572 | + return |
573 | + |
574 | + act = QtGui.QAction("Add DemoWidget", splitter) |
575 | + def wrapper(): |
576 | + splitter.replace_widget(splitter.widget(index), |
577 | + DemoWidget(splitter)) |
578 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
579 | + menu.addAction(act) |
580 | + |
581 | + act = QtGui.QAction("Add DemoWidget Nest", splitter) |
582 | + def wrapper(): |
583 | + splitter.replace_widget(splitter.widget(index), |
584 | + demo_nest(splitter)) |
585 | + act.connect(act, Qt.SIGNAL('triggered()'), wrapper) |
586 | + menu.addAction(act) |
587 | + |
588 | + # example - remove "Remove" commands from handle context menu |
589 | + def callback2(menu, splitter, index, button_mode): |
590 | + |
591 | + if button_mode: |
592 | + return |
593 | + |
594 | + for i in [i for i in menu.actions() |
595 | + if str(i.objectName()).startswith('remove')]: |
596 | + menu.removeAction(i) |
597 | + |
598 | + # more complex example only removing remove action for particular target |
599 | + def dont_close_special(menu, splitter, index, button_mode): |
600 | + |
601 | + if button_mode: |
602 | + return |
603 | + |
604 | + widget, neighbour, count = splitter.handle_context(index) |
605 | + |
606 | + for i in 0,1: |
607 | + if (widget[i] == special or |
608 | + neighbour[i] and neighbour[i].contains(special)): |
609 | + |
610 | + for a in [a for a in menu.actions() |
611 | + if a.objectName() == "remove %d"%i]: |
612 | + menu.removeAction(i) |
613 | + |
614 | + |
615 | + app = Qt.QApplication(sys.argv) |
616 | + |
617 | + wdg = DemoWidget() |
618 | + wdg2 = DemoWidget() |
619 | + |
620 | + splitter = NestedSplitter() |
621 | + splitter.addWidget(wdg) |
622 | + splitter.addWidget(wdg2) |
623 | + |
624 | + splitter.register(callback) |
625 | + # splitter.register(callback2) |
626 | + |
627 | + holder = QtGui.QWidget() |
628 | + holder.setLayout(QtGui.QVBoxLayout()) |
629 | + holder.layout().setContentsMargins(QtCore.QMargins(0,0,0,0)) |
630 | + holder.layout().addWidget(splitter) |
631 | + holder.show() |
632 | + |
633 | + app.exec_() |
634 | |
635 | === modified file 'leo/plugins/qtGui.py' |
636 | --- leo/plugins/qtGui.py 2011-02-19 18:54:19 +0000 |
637 | +++ leo/plugins/qtGui.py 2011-02-19 19:01:52 +0000 |
638 | @@ -62,6 +62,16 @@ |
639 | print('\nqtGui.py: can not import Qt\nUse "launchLeo.py --gui=tk" to force Tk') |
640 | raise |
641 | |
642 | +try: |
643 | + from nested_splitter import NestedSplitter |
644 | + splitter_class = NestedSplitter |
645 | + |
646 | + # disable special behavior, turned back on by associated plugin, |
647 | + # if the plugin's loaded |
648 | + NestedSplitter.enabled = False |
649 | +except ImportError: |
650 | + splitter_class = QtGui.QSplitter |
651 | + |
652 | # remove scintilla dep for now |
653 | if 0: |
654 | try: |
655 | @@ -1978,7 +1988,7 @@ |
656 | vLayout = self.createVLayout(parent,'mainVLayout',margin=3) |
657 | |
658 | # Splitter two is the "main" splitter, containing splitter. |
659 | - splitter2 = QtGui.QSplitter(parent) |
660 | + splitter2 = splitter_class(parent) |
661 | splitter2.setOrientation(QtCore.Qt.Vertical) |
662 | splitter2.setObjectName("splitter_2") |
663 | |
664 | @@ -1986,7 +1996,7 @@ |
665 | QtCore.SIGNAL("splitterMoved(int,int)"), |
666 | self.onSplitter2Moved) |
667 | |
668 | - splitter = QtGui.QSplitter(splitter2) |
669 | + splitter = splitter_class(splitter2) |
670 | splitter.setOrientation(QtCore.Qt.Horizontal) |
671 | splitter.setObjectName("splitter") |
672 |
On Sat, Feb 19, 2011 at 1:02 PM, tbnorth <email address hidden> wrote:
> tbnorth has proposed merging lp:~terry-n-brown/leo-editor/free_layout into lp:leo-editor.
Do I need to do anything to approve this? If not, please just merge,
assuming all unit tests pass.
Edward