@@ -231,6 +231,7 @@ def __init__(self, rootfile=None, tdir="femto-producer"):
231231 self ._is_filter_hist = False
232232 self ._vars = {} # (SelectionName, idx) → BooleanVar
233233 self ._check_labels = {} # (SelectionName, idx) → Label (custom checkbox glyph)
234+ self ._setters = {} # (SelectionName, idx) → callable(bool) setting the checkbox state + glyph
234235
235236 self ._build_ui ()
236237
@@ -264,6 +265,26 @@ def _build_ui(self):
264265 self ._hist_combo .pack (side = "left" , padx = 6 )
265266 self ._hist_combo .bind ("<<ComboboxSelected>>" , self ._on_hist_selected )
266267
268+ # ── bitmask input: check all cuts corresponding to a given bitmask ──
269+ tk .Label (bar , text = "Bitmask:" , font = FONT_BODY , bg = BG_CARD , fg = FG_DIM ).pack (side = "left" , padx = (20 , 0 ))
270+ self ._mask_var = tk .StringVar ()
271+ mask_entry = tk .Entry (
272+ bar ,
273+ textvariable = self ._mask_var ,
274+ width = 16 ,
275+ font = FONT_BODY ,
276+ bg = BG ,
277+ fg = FG ,
278+ insertbackground = FG ,
279+ relief = "flat" ,
280+ highlightthickness = 1 ,
281+ highlightbackground = BORDER ,
282+ highlightcolor = ACCENT ,
283+ )
284+ mask_entry .pack (side = "left" , padx = 6 )
285+ mask_entry .bind ("<Return>" , lambda _e : self ._apply_bitmask ())
286+ self ._make_button (bar , "Set Bitmask" , self ._apply_bitmask , ACCENT ).pack (side = "left" , padx = 2 )
287+
267288 self ._style_combobox ()
268289
269290 # ── legend ──
@@ -470,6 +491,7 @@ def _on_hist_selected(self, _e=None):
470491 self ._hist = hist
471492 self ._vars = {}
472493 self ._check_labels = {}
494+ self ._setters = {}
473495
474496 # Always start from a clean summary panel — forget()-ing the pane only
475497 # hides it, it does not destroy previously built rows.
@@ -714,10 +736,15 @@ def _build_loosest_row(self, parent, sel_name, idx, b):
714736 self ._vars [(sel_name , idx )] = var
715737 self ._check_labels [(sel_name , idx )] = check_lbl
716738
739+ def set_state (state ):
740+ var .set (state )
741+ check_lbl .config (text = "[x]" if state else "[ ]" , fg = ACCENT_ALWAYS if state else FG_DIM )
742+ text_lbl .config (fg = FG if state else FG_DIM )
743+
744+ self ._setters [(sel_name , idx )] = set_state
745+
717746 def toggle (_e = None ):
718- var .set (not var .get ())
719- check_lbl .config (text = "[x]" if var .get () else "[ ]" , fg = ACCENT_ALWAYS if var .get () else FG_DIM )
720- text_lbl .config (fg = FG if var .get () else FG_DIM )
747+ set_state (not var .get ())
721748 self ._update_bitmask ()
722749
723750 for w in (row , check_lbl , text_lbl ):
@@ -752,14 +779,55 @@ def _build_bin_row(self, parent, sel_name, idx, b, kind):
752779 if pos .upper () != "X" :
753780 tk .Label (row , text = f"bit { pos } " , font = FONT_SMALL , bg = BG_CARD , fg = FG_DIM , width = 8 ).pack (side = "right" , padx = 4 )
754781
782+ def set_state (state ):
783+ var .set (state )
784+ check_lbl .config (text = "[x]" if state else "[ ]" , fg = color if state else FG_DIM )
785+
786+ self ._setters [(sel_name , idx )] = set_state
787+
755788 def toggle (_e = None ):
756- var .set (not var .get ())
757- check_lbl .config (text = "[x]" if var .get () else "[ ]" , fg = color if var .get () else FG_DIM )
789+ set_state (not var .get ())
758790 self ._update_bitmask ()
759791
760792 for w in (row , check_lbl , text_lbl ):
761793 w .bind ("<Button-1>" , toggle )
762794
795+ # ── Bitmask input ─────────────────────────────────────────────────────────
796+ def _apply_bitmask (self ):
797+ """Check exactly the cuts whose bit is set in the entered bitmask. The
798+ always-applied minimal floors are added to the summary automatically."""
799+ if self ._is_filter_hist or not self ._groups :
800+ messagebox .showinfo ("Not applicable" , "Load a selection histogram first." )
801+ return
802+
803+ text = self ._mask_var .get ().strip ()
804+ try :
805+ bitmask = int (text , 0 )
806+ except ValueError :
807+ try :
808+ # int(x, 0) rejects decimals with leading zeros, e.g. "010"
809+ bitmask = int (text , 10 )
810+ except ValueError :
811+ bitmask = - 1
812+ if bitmask < 0 :
813+ messagebox .showerror ("Invalid bitmask" , f"Cannot parse '{ text } '.\n Use decimal, 0x hex or 0b binary." )
814+ return
815+
816+ known_bits = set ()
817+ for (sel_name , idx ), set_state in self ._setters .items ():
818+ pos = bit_position_int (self ._groups [sel_name ][idx ])
819+ if pos >= 0 :
820+ known_bits .add (pos )
821+ set_state (pos >= 0 and bool (bitmask & (1 << pos )))
822+ self ._update_bitmask ()
823+
824+ unknown_bits = [i for i in range (bitmask .bit_length ()) if bitmask & (1 << i ) and i not in known_bits ]
825+ if unknown_bits :
826+ messagebox .showwarning (
827+ "Unknown bits" ,
828+ f"Bit(s) { ', ' .join (map (str , unknown_bits ))} are set but not defined in this histogram." ,
829+ )
830+
763831 # ── Bitmask computation + summary update ──────────────────────────────────
764832 def _update_bitmask (self ):
765833 if self ._is_filter_hist :
0 commit comments