@@ -51,6 +51,7 @@ def __init__(self, config, parent=None):
5151 QColor ("#2980b9" ), # Strong blue
5252 ]
5353 self ._init_style_widgets ()
54+ self .pureref_manager = None
5455
5556 def _init_style_widgets (self ):
5657 """Create widgets to retrieve styles from QSS."""
@@ -167,6 +168,32 @@ def get_play_button_rect(self, rect, index=None):
167168 logging .error (f"Error in get_play_button_rect: { e } " )
168169 return QRect ()
169170
171+ def get_pureref_badge_rect (self , icon_rect : QRectF ) -> QRectF :
172+ """Return PureRef badge rect at top-right corner of folder icon."""
173+ badge_size = 28 # Same as play button
174+ margin = 3
175+ return QRectF (
176+ icon_rect .right () - badge_size - margin ,
177+ icon_rect .top () + margin ,
178+ badge_size ,
179+ badge_size ,
180+ )
181+
182+ def _get_folder_icon_rect (self , rect , index ) -> QRectF :
183+ """Calculate folder icon rectangle."""
184+ chain = self ._get_nesting_chain (index )
185+ # Call _draw_nesting_lines with painter=None to get offset without drawing
186+ nesting_offset = self ._draw_nesting_lines (None , rect , chain , index )
187+ icon_height = self .config .get ("folder_row_height" , 70 ) - 14
188+ icon_width = int (icon_height * 16 / 9 )
189+ center_y = rect .center ().y ()
190+ return QRectF (
191+ rect .left () + nesting_offset + 5 ,
192+ center_y - icon_height / 2 - 2 ,
193+ icon_width ,
194+ icon_height ,
195+ )
196+
170197 def sizeHint (self , option , index ):
171198 try :
172199 # import sys; sys.stderr.write(f"[SIZEHINT] row={index.row()}\n"); sys.stderr.flush()
@@ -246,6 +273,10 @@ def _draw_nesting_lines(self, painter, rect, chain, index=None):
246273
247274 spacing = max (2 , indent - line_width )
248275
276+ # If painter is None, we're only calculating offset, not drawing
277+ if painter is None :
278+ return line_width + spacing
279+
249280 # Determine if this item is the last child of its immediate parent
250281 is_last_child = False
251282 if index is not None and index .isValid () and index .parent ().isValid ():
@@ -934,6 +965,25 @@ def get_marker_at_pos(self, rect, pos, index):
934965 def editorEvent (self , event , model , option , index ):
935966 try :
936967 if event .type () == QEvent .Type .MouseButtonRelease :
968+ item_type = index .data (Qt .ItemDataRole .UserRole + 1 )
969+
970+ # Handle PureRef badge click for folders
971+ if item_type == "folder" and getattr (self , "pureref_manager" , None ):
972+ folder_path = index .data (Qt .ItemDataRole .UserRole )
973+ root_path = index .data (Qt .ItemDataRole .UserRole + 3 )
974+ if folder_path and root_path :
975+ full_folder = Path (root_path ) / folder_path
976+ icon_rect = self ._get_folder_icon_rect (option .rect , index )
977+ badge_rect = self .get_pureref_badge_rect (icon_rect )
978+ if badge_rect .contains (QPointF (event .pos ())):
979+ tree = self .parent ()
980+ if tree :
981+ window = tree .window ()
982+ if hasattr (window , "open_pureref_for_folder" ):
983+ window .open_pureref_for_folder (full_folder )
984+ return True
985+
986+ # Original marker logic
937987 marker = self .get_marker_at_pos (option .rect , event .pos (), index )
938988 if marker :
939989 file_path = index .data (Qt .ItemDataRole .UserRole )
@@ -1024,9 +1074,9 @@ def paint_folder(self, painter, option, index):
10241074 painter .setClipPath (clip_path )
10251075
10261076 painter .drawPixmap (target_rect , scaled_pixmap , QRectF (scaled_pixmap .rect ()))
1077+ painter .restore ()
10271078
10281079 # Draw border
1029- painter .restore ()
10301080 painter .setClipping (False )
10311081 painter .setPen (
10321082 self .thumbnail_border .palette ().color (QPalette .ColorRole .Mid )
@@ -1098,6 +1148,8 @@ def paint_folder(self, painter, option, index):
10981148 )
10991149 painter .drawRoundedRect (icon_rect , 3 , 3 )
11001150
1151+ painter .restore ()
1152+
11011153 # Draw Folder Progress Bar
11021154 progress = index .data (Qt .ItemDataRole .UserRole + 6 )
11031155 if progress is not None :
@@ -1258,6 +1310,72 @@ def paint_folder(self, painter, option, index):
12581310
12591311 painter .drawLine (line_start_x , y_pos , option .rect .right (), y_pos )
12601312
1313+ # Draw PureRef button in top-right corner
1314+ if getattr (self , "pureref_manager" , None ):
1315+ folder_path = index .data (Qt .ItemDataRole .UserRole )
1316+ root_path = index .data (Qt .ItemDataRole .UserRole + 3 )
1317+ if folder_path and root_path :
1318+ full_folder = Path (root_path ) / folder_path
1319+ has_file = self .pureref_manager .has_pur_file (full_folder )
1320+
1321+ # Check settings: show badges and show when missing
1322+ show_badges = self .config .get ("show_pureref_badges" , True )
1323+ show_when_missing = self .config .get (
1324+ "show_pureref_badges_when_missing" , False
1325+ )
1326+
1327+ # Logic: if show_badges is False, don't show at all
1328+ # If show_badges is True, show only if file exists OR show_when_missing is True
1329+ should_show = show_badges and (has_file or show_when_missing )
1330+
1331+ if should_show :
1332+ badge_rect = self .get_pureref_badge_rect (icon_rect )
1333+ is_open = self .pureref_manager .is_running (full_folder )
1334+
1335+ is_over_badge = False
1336+ if self .mouse_pos and badge_rect .contains (QPointF (self .mouse_pos )):
1337+ is_over_badge = True
1338+
1339+ painter .save ()
1340+ painter .setRenderHint (QPainter .RenderHint .Antialiasing )
1341+
1342+ # Draw button background (Play button style, but always fully visible)
1343+ bg_color = self .row_play_button_bg .palette ().color (
1344+ QPalette .ColorRole .Window
1345+ )
1346+ if is_over_badge :
1347+ painter .setBrush (bg_color .lighter (110 ))
1348+ else :
1349+ painter .setBrush (bg_color )
1350+
1351+ painter .setPen (Qt .PenStyle .NoPen )
1352+ painter .drawRoundedRect (badge_rect , 3 , 3 )
1353+
1354+ # Draw "P" letter
1355+ painter .setPen (QColor ("#ffffff" ))
1356+ font = painter .font ()
1357+ font .setPixelSize (14 )
1358+ font .setBold (True )
1359+ painter .setFont (font )
1360+ painter .drawText (badge_rect , Qt .AlignmentFlag .AlignCenter , "P" )
1361+
1362+ # Draw status dot
1363+ if not has_file :
1364+ status_color = QColor ("#e74c3c" ) # Red (Missing)
1365+ elif is_open :
1366+ status_color = QColor ("#2ecc71" ) # Green (Running)
1367+ else :
1368+ status_color = QColor ("#f9ca24" ) # Yellow (Exists)
1369+
1370+ painter .setBrush (status_color )
1371+ painter .setPen (Qt .PenStyle .NoPen )
1372+ painter .drawEllipse (
1373+ QRectF (badge_rect .right () - 8 , badge_rect .top () + 2 , 6 , 6 )
1374+ )
1375+ painter .restore ()
1376+
1377+ painter .restore ()
1378+
12611379 painter .restore ()
12621380
12631381
@@ -1328,6 +1446,38 @@ def mouseMoveEvent(self, event):
13281446 index , self .thumbnail_frame , mouse_pos = event .pos ()
13291447 )
13301448
1449+ self ._update_item_rect (index )
1450+ return
1451+ elif item_type == "folder" :
1452+ delegate = self .itemDelegate ()
1453+ if isinstance (delegate , VideoItemDelegate ):
1454+ if index != self .current_hover_index :
1455+ prev_index = self .current_hover_index
1456+ self .current_hover_index = index
1457+ self .hover_timer .stop ()
1458+ delegate .set_hovered_index (index , 0 , mouse_pos = event .pos ())
1459+ if prev_index is not None and prev_index .isValid ():
1460+ self ._update_item_rect (prev_index )
1461+ else :
1462+ delegate .set_hovered_index (index , 0 , mouse_pos = event .pos ())
1463+
1464+ visual_rect = self .visualRect (index )
1465+ if not visual_rect .isNull ():
1466+ icon_rect = delegate ._get_folder_icon_rect (
1467+ visual_rect , index
1468+ )
1469+ badge_rect = delegate .get_pureref_badge_rect (icon_rect )
1470+
1471+ # Check if mouse is over icon or badge
1472+ if icon_rect .contains (
1473+ QPointF (event .pos ())
1474+ ) or badge_rect .contains (QPointF (event .pos ())):
1475+ self .viewport ().setCursor (
1476+ Qt .CursorShape .PointingHandCursor
1477+ )
1478+ else :
1479+ self .viewport ().setCursor (Qt .CursorShape .ArrowCursor )
1480+
13311481 self ._update_item_rect (index )
13321482 return
13331483 else :
@@ -1346,24 +1496,65 @@ def mousePressEvent(self, event):
13461496 if index .isValid ():
13471497 delegate = self .itemDelegate ()
13481498 if isinstance (delegate , VideoItemDelegate ):
1349- # Check hover over play button
1350- play_rect = delegate .get_play_button_rect (self .visualRect (index ), index )
1351-
1352- if play_rect .contains (event .pos ()):
1353- item = self .itemFromIndex (index )
1354- if item :
1355- file_path = item .data (0 , Qt .ItemDataRole .UserRole )
1356- # If already playing video - toggle pause
1357- main_window = self .window ()
1358- delegate = self .itemDelegate ()
1359- if delegate .playing_path == file_path :
1360- if hasattr (main_window , "video_player" ):
1361- main_window .video_player .play_pause ()
1362- else :
1363- # Otherwise start new
1364- if hasattr (main_window , "play_video_in_player" ):
1365- main_window .play_video_in_player (item , resume = True )
1366- return # Stop processing to avoid standard row selection
1499+ item_type = index .data (Qt .ItemDataRole .UserRole + 1 )
1500+
1501+ # Handle video play button clicks
1502+ if item_type == "video" :
1503+ play_rect = delegate .get_play_button_rect (
1504+ self .visualRect (index ), index
1505+ )
1506+ if play_rect .contains (event .pos ()):
1507+ item = self .itemFromIndex (index )
1508+ if item :
1509+ file_path = item .data (0 , Qt .ItemDataRole .UserRole )
1510+ # If already playing video - toggle pause
1511+ main_window = self .window ()
1512+ delegate = self .itemDelegate ()
1513+ if delegate .playing_path == file_path :
1514+ if hasattr (main_window , "video_player" ):
1515+ main_window .video_player .play_pause ()
1516+ else :
1517+ # Otherwise start new
1518+ if hasattr (main_window , "play_video_in_player" ):
1519+ main_window .play_video_in_player (item , resume = True )
1520+ return # Stop processing to avoid standard row selection
1521+
1522+ # Handle folder icon/badge clicks
1523+ elif item_type == "folder" :
1524+ visual_rect = self .visualRect (index )
1525+ if not visual_rect .isNull ():
1526+ icon_rect = delegate ._get_folder_icon_rect (visual_rect , index )
1527+
1528+ # Check if PureRef badges should be shown
1529+ show_badges = delegate .config .get ("show_pureref_badges" , True )
1530+ show_when_missing = delegate .config .get (
1531+ "show_pureref_badges_when_missing" , False
1532+ )
1533+
1534+ if show_badges and getattr (delegate , "pureref_manager" , None ):
1535+ folder_path = index .data (Qt .ItemDataRole .UserRole )
1536+ root_path = index .data (Qt .ItemDataRole .UserRole + 3 )
1537+ if folder_path and root_path :
1538+ full_folder = Path (root_path ) / folder_path
1539+ has_file = delegate .pureref_manager .has_pur_file (
1540+ full_folder
1541+ )
1542+ should_show_badge = has_file or show_when_missing
1543+
1544+ if should_show_badge :
1545+ badge_rect = delegate .get_pureref_badge_rect (
1546+ icon_rect
1547+ )
1548+ # If click is on icon or badge, open PureRef
1549+ if icon_rect .contains (
1550+ QPointF (event .pos ())
1551+ ) or badge_rect .contains (QPointF (event .pos ())):
1552+ item = self .itemFromIndex (index )
1553+ if item :
1554+ main_window = self .window ()
1555+ if hasattr (main_window , "open_pureref" ):
1556+ main_window .open_pureref (item )
1557+ return # Stop processing to avoid standard row selection
13671558
13681559 super ().mousePressEvent (event )
13691560
0 commit comments