30
30
31
31
32
32
@property_mixin
33
- class ScrollableMenuMixin ( object ):
33
+ class ScrollableMenuBase ( QtWidgets . QMenu ):
34
34
"""
35
35
https://www.pythonfixing.com/2021/10/fixed-how-to-have-scrollable-context.html
36
36
"""
@@ -40,7 +40,7 @@ class ScrollableMenuMixin(object):
40
40
ignoreAutoScroll = False
41
41
42
42
def __init__ (self , * args , ** kwargs ):
43
- super (ScrollableMenuMixin , self ).__init__ (* args , ** kwargs )
43
+ super (ScrollableMenuBase , self ).__init__ (* args , ** kwargs )
44
44
self ._maximumHeight = self .maximumHeight ()
45
45
self ._actionRects = []
46
46
@@ -53,15 +53,15 @@ def __init__(self, *args, **kwargs):
53
53
self .setMaxItemCount (0 )
54
54
55
55
def _set_max_scroll_count (self , value ):
56
- self .setMaxItemCount (value )
56
+ self .setMaxItemCount (value * 2.2 )
57
57
58
58
@property
59
59
def actionRects (self ):
60
60
if self .dirty or not self ._actionRects :
61
61
del self ._actionRects [:]
62
62
offset = self .offset ()
63
63
for action in self .actions ():
64
- geo = super (ScrollableMenuMixin , self ).actionGeometry (action )
64
+ geo = super (ScrollableMenuBase , self ).actionGeometry (action )
65
65
if offset :
66
66
geo .moveTop (geo .y () - offset )
67
67
self ._actionRects .append (geo )
@@ -105,7 +105,10 @@ def scrollHeight(self, style):
105
105
return style .pixelMetric (style .PM_MenuScrollerHeight , None , self ) * 2
106
106
107
107
def isScrollable (self ):
108
- return self .height () < super (ScrollableMenuMixin , self ).sizeHint ().height ()
108
+ return (
109
+ self .property ("scrollable" )
110
+ and self .height () < super (ScrollableMenuBase , self ).sizeHint ().height ()
111
+ )
109
112
110
113
def checkScroll (self ):
111
114
pos = self .mapFromGlobal (QtGui .QCursor .pos ())
@@ -174,7 +177,7 @@ def actionAt(self, pos):
174
177
# class methods reimplementation
175
178
176
179
def sizeHint (self ):
177
- hint = super (ScrollableMenuMixin , self ).sizeHint ()
180
+ hint = super (ScrollableMenuBase , self ).sizeHint ()
178
181
if hint .height () > self .maximumHeight ():
179
182
hint .setHeight (self .maximumHeight ())
180
183
return hint
@@ -188,16 +191,16 @@ def eventFilter(self, source, event):
188
191
delta = rect .topLeft () - self .actionGeometry (action ).topLeft ()
189
192
source .move (source .pos () + delta )
190
193
return False
191
- return super (ScrollableMenuMixin , self ).eventFilter (source , event )
194
+ return super (ScrollableMenuBase , self ).eventFilter (source , event )
192
195
193
196
def event (self , event ):
194
197
if not self .isScrollable ():
195
- return super (ScrollableMenuMixin , self ).event (event )
198
+ return super (ScrollableMenuBase , self ).event (event )
196
199
if event .type () == event .KeyPress and event .key () in (
197
200
QtCore .Qt .Key_Up ,
198
201
QtCore .Qt .Key_Down ,
199
202
):
200
- res = super (ScrollableMenuMixin , self ).event (event )
203
+ res = super (ScrollableMenuBase , self ).event (event )
201
204
action = self .activeAction ()
202
205
if action :
203
206
self .ensureVisible (action )
@@ -225,16 +228,16 @@ def event(self, event):
225
228
action .trigger ()
226
229
self .close ()
227
230
return True
228
- return super (ScrollableMenuMixin , self ).event (event )
231
+ return super (ScrollableMenuBase , self ).event (event )
229
232
230
233
def timerEvent (self , event ):
231
234
if not self .isScrollable ():
232
235
# ignore internal timer event for reopening popups
233
- super (ScrollableMenuMixin , self ).timerEvent (event )
236
+ super (ScrollableMenuBase , self ).timerEvent (event )
234
237
235
238
def mouseMoveEvent (self , event ):
236
239
if not self .isScrollable ():
237
- super (ScrollableMenuMixin , self ).mouseMoveEvent (event )
240
+ super (ScrollableMenuBase , self ).mouseMoveEvent (event )
238
241
return
239
242
240
243
pos = event .pos ()
@@ -295,16 +298,16 @@ def showEvent(self, event):
295
298
if action .menu ():
296
299
action .menu ().installEventFilter (self )
297
300
self .ignoreAutoScroll = False
298
- super (ScrollableMenuMixin , self ).showEvent (event )
301
+ super (ScrollableMenuBase , self ).showEvent (event )
299
302
300
303
def hideEvent (self , event ):
301
304
for action in self .actions ():
302
305
if action .menu ():
303
306
action .menu ().removeEventFilter (self )
304
- super (ScrollableMenuMixin , self ).hideEvent (event )
307
+ super (ScrollableMenuBase , self ).hideEvent (event )
305
308
306
309
def resizeEvent (self , event ):
307
- super (ScrollableMenuMixin , self ).resizeEvent (event )
310
+ super (ScrollableMenuBase , self ).resizeEvent (event )
308
311
309
312
style = self .style ()
310
313
l , t , r , b = self .getContentsMargins ()
@@ -329,7 +332,7 @@ def resizeEvent(self, event):
329
332
330
333
def paintEvent (self , event ):
331
334
if not self .isScrollable ():
332
- super (ScrollableMenuMixin , self ).paintEvent (event )
335
+ super (ScrollableMenuBase , self ).paintEvent (event )
333
336
return
334
337
335
338
style = self .style ()
@@ -345,7 +348,6 @@ def paintEvent(self, event):
345
348
style .drawPrimitive (style .PE_PanelMenu , menuOpt , qp , self )
346
349
347
350
fw = style .pixelMetric (style .PM_MenuPanelWidth , None , self )
348
-
349
351
topEdge = self .scrollUpRect .bottom ()
350
352
bottomEdge = self .scrollDownRect .top ()
351
353
offset = self .offset ()
@@ -432,7 +434,31 @@ def paintEvent(self, event):
432
434
433
435
434
436
@property_mixin
435
- class SearchableMenuMixin (object ):
437
+ class SearchableMenuBase (ScrollableMenuBase ):
438
+ def __init__ (self , * args , ** kwargs ):
439
+ super (SearchableMenuBase , self ).__init__ (* args , ** kwargs )
440
+ self .search_popup = MPopup (self )
441
+ self .search_popup .setVisible (False )
442
+ self .search_bar = MLineEdit (self )
443
+ self .search_label = QtWidgets .QLabel ()
444
+
445
+ self .search_bar .textChanged .connect (self .slot_search_change )
446
+ self .search_bar .keyPressEvent = partial (
447
+ self .search_key_event , self .search_bar .keyPressEvent
448
+ )
449
+ self .aboutToHide .connect (lambda : self .search_bar .setText ("" ))
450
+
451
+ layout = QtWidgets .QVBoxLayout ()
452
+ layout .addWidget (self .search_label )
453
+ layout .addWidget (self .search_bar )
454
+ self .search_popup .setLayout (layout )
455
+
456
+ self .setProperty ("search_placeholder" , self .tr ("Search Action..." ))
457
+ self .setProperty ("search_label" , self .tr ("Search Action..." ))
458
+
459
+ self .setProperty ("searchable" , True )
460
+ self .setProperty ("search_re" , "I" )
461
+
436
462
def search_key_event (self , call , event ):
437
463
key = event .key ()
438
464
# NOTES: support menu original key event on search bar
@@ -442,42 +468,26 @@ def search_key_event(self, call, event):
442
468
QtCore .Qt .Key_Return ,
443
469
QtCore .Qt .Key_Enter ,
444
470
):
445
- super (SearchableMenuMixin , self ).keyPressEvent (event )
471
+ super (SearchableMenuBase , self ).keyPressEvent (event )
446
472
elif key == QtCore .Qt .Key_Tab :
447
473
self .search_bar .setFocus ()
448
474
return call (event )
449
475
450
- def search (self ):
451
- self .setStyleSheet ("QMenu{menu-scrollable: 1;}" )
452
- self .setProperty ("search" , True )
453
- self .search_popup = MPopup (self )
454
- layout = QtWidgets .QVBoxLayout ()
455
-
456
- self .search_bar = MLineEdit (self )
457
- self .search_bar .keyPressEvent = partial (
458
- self .search_key_event , self .search_bar .keyPressEvent
459
- )
460
- self .search_bar .setPlaceholderText (self .tr ("Search Action..." ))
461
- self .search_bar .textChanged .connect (self .slot_search_change )
462
- self .search_label = QtWidgets .QLabel (self .tr ("Search Action..." ))
463
- self .search_label .setAttribute (QtCore .Qt .WA_TransparentForMouseEvents )
464
- layout .addWidget (self .search_label )
465
- layout .addWidget (self .search_bar )
466
- self .search_popup .setLayout (layout )
467
-
468
- self .aboutToHide .connect (lambda : self .search_bar .setText ("" ))
469
-
470
- def _set_search (self , value ):
471
- value and self .search ()
472
-
473
476
def _set_search_label (self , value ):
474
- self .property ( "search" ) and self . search_label .setText (value )
477
+ self .search_label .setText (value )
475
478
476
479
def _set_search_placeholder (self , value ):
477
- self .property ("search" ) and self .search_bar .setPlaceholderText (value )
480
+ self .search_bar .setPlaceholderText (value )
481
+
482
+ def _set_search_re (self , value ):
483
+ if not isinstance (value , six .text_type ):
484
+ raise TypeError ("`search_re` property should be a string type" )
478
485
479
486
def slot_search_change (self , text ):
480
- search_reg = re .compile (r".*%s.*" % text )
487
+ flags = 0
488
+ for m in self .property ("search_re" ) or "" :
489
+ flags |= getattr (re , m .upper (), 0 )
490
+ search_reg = re .compile (r".*%s.*" % text , flags )
481
491
self ._update_search (search_reg )
482
492
483
493
def _update_search (self , search_reg , parent_menu = None ):
@@ -498,26 +508,26 @@ def _update_search(self, search_reg, parent_menu=None):
498
508
499
509
def keyPressEvent (self , event ):
500
510
key = event .key ()
501
- if self .property ("search " ):
511
+ if self .property ("searchable " ):
502
512
# NOTES(timmyliang): 26 character trigger search bar
503
513
if 65 <= key <= 90 :
504
514
char = chr (key )
505
515
self .search_bar .setText (char )
506
516
self .search_bar .setFocus ()
507
517
self .search_bar .selectAll ()
508
- self .search_popup .show ()
509
518
width = self .sizeHint ().width ()
510
519
width = width if width >= 50 else 50
511
520
offset = QtCore .QPoint (width , 0 )
512
521
self .search_popup .move (self .pos () + offset )
522
+ self .search_popup .show ()
513
523
elif key == QtCore .Qt .Key_Escape :
514
524
self .search_bar .setText ("" )
515
- self .search_bar .hide ()
516
- return super (SearchableMenuMixin , self ).keyPressEvent (event )
525
+ self .search_popup .hide ()
526
+ return super (SearchableMenuBase , self ).keyPressEvent (event )
517
527
518
528
519
529
@property_mixin
520
- class MMenu (SearchableMenuMixin , ScrollableMenuMixin , QtWidgets . QMenu ):
530
+ class MMenu (SearchableMenuBase ):
521
531
sig_value_changed = QtCore .Signal (object )
522
532
523
533
def __init__ (self , exclusive = True , cascader = False , title = "" , parent = None ):
0 commit comments