Skip to content

Add dynamic column management and zoom features#12

Open
gdevenyi wants to merge 1 commit intomasterfrom
local-vibe-improvements
Open

Add dynamic column management and zoom features#12
gdevenyi wants to merge 1 commit intomasterfrom
local-vibe-improvements

Conversation

@gdevenyi
Copy link
Copy Markdown
Member

@gdevenyi gdevenyi commented Apr 7, 2026

  • Add right-click context menu for column operations (add/rename/remove)
  • Implement zoom options in View menu (1:1, fit, in, out)
  • Auto-fit image on window resize when zoom to fit is active
  • Add Quit option to File menu
  • Fix column auto-resize for all column operations
  • Remove redundant f.close() calls
  • Update AGENTS.md with dynamic column management instructions

Summary by CodeRabbit

  • New Features

    • Dynamic rating column management: add, rename, and remove columns via right-click header menu.
    • Zoom controls: fit-to-window, 1:1 scale, zoom in/out with keyboard shortcuts.
    • Enhanced keyboard navigation for rating columns with auto-advance to next rows.
  • Documentation

    • Added setup and usage guide with application architecture overview.

- Add right-click context menu for column operations (add/rename/remove)
- Implement zoom options in View menu (1:1, fit, in, out)
- Auto-fit image on window resize when zoom to fit is active
- Add Quit option to File menu
- Fix column auto-resize for all column operations
- Remove redundant f.close() calls
- Update AGENTS.md with dynamic column management instructions
Copilot AI review requested due to automatic review settings April 7, 2026 13:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

This pull request introduces dynamic rating column management and zoom functionality to the PyQt5-based image quality control application. It replaces wildcard imports with explicit Qt imports, refactors CSV loading, adds keyboard-driven column editing via a header context menu, implements zoom/fit-to-window controls, and updates UI definitions accordingly. Documentation is added via AGENTS.md.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md
New guide documenting application setup via uv, CLI/GUI entry points, architecture (PyQC.py, window1.py, image_widget.py), code-generation workflow for UI updates, and dynamic column management behavior via right-click header context menu.
Core Application Logic
PyQC.py
Replaced wildcard Qt imports with explicit imports; added dynamic column management (self.column_names, addColumn(), renameColumn(), removeColumn()); refactored CSV loading to read once and derive columns from header; updated rating navigation to cycle through dynamic rating columns; implemented zoom (zoomTo1_1(), zoomToFit(), zoomIn(), zoomOut()) and resize handlers; added showColumnContextMenu() for header right-click editing.
UI Definition Files
window1.ui, window1.py
Updated UI header version (5.15.7 → 5.15.11); added menu_Columns menu with column management actions; added zoom and quit actions with keyboard shortcuts; configured table widget with CustomContextMenu policy, handle width, and maximum size constraints; refactored splitter/table layout structure.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as MainWindow
    participant Menu as Context Menu
    participant Table as QTableWidget

    User->>UI: Right-click table header
    UI->>Menu: showColumnContextMenu()
    Menu->>User: Display "Add/Rename/Remove Column"
    
    alt Add Column
        User->>Menu: Select "Add Column"
        Menu->>UI: addColumn()
        UI->>Table: Insert new column
        UI->>Table: Update headers
        UI->>Table: Fill cells for all rows
        Table->>UI: Refresh display
    else Rename Column
        User->>Menu: Select "Rename Column"
        Menu->>UI: renameColumn()
        UI->>UI: Prompt for new name
        UI->>Table: Update header text
        Table->>UI: Refresh display
    else Remove Column
        User->>Menu: Select "Remove Column"
        Menu->>UI: Confirm removal
        UI->>Table: Remove selected column
        UI->>Table: Update headers
        Table->>UI: Refresh display
    end
    
    UI->>User: Updated table with modified columns
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes


🐰 New columns bloom in the header's right-click spring,
Zoom and fit make images dance just right,
CSV rows now parse with dynamic fling,
Rating columns bend to user's delight,
The table grows clever, a QC's true wing!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: dynamic column management (add/rename/remove) and zoom features (1:1, fit, zoom in/out) are the primary focus across the modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch local-vibe-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds UI and runtime support for managing QC rating columns at runtime and introduces explicit zoom controls (menu actions + resize-to-fit behavior), along with dependency/doc updates.

Changes:

  • Adds column-management UI (Columns menu + custom header/table context menu) and supporting handlers.
  • Adds View menu zoom actions (1:1, fit, in/out) plus window-resize handling to re-fit when appropriate.
  • Introduces uv.lock and updates agent documentation/codegen notes.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
window1.ui Adds context-menu policy, new menus/actions (File/View/Columns), splitter handle width; attempts to cap table width.
window1.py Regenerates UI bindings for new menus/actions and context-menu policy; adjusts splitter/table sizing.
PyQC.py Wires new menu actions, implements zoom + column add/rename/remove + CSV parsing updates for dynamic columns.
uv.lock Adds uv lockfile capturing resolved dependencies (PyQt5, sip, Qt5).
AGENTS.md Documents setup, codegen workflow, and dynamic column management usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +52 to +63
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window1.ui defines maximumSize for tableWidget twice (first width=500, then width=16777215). In Qt Designer XML, the later property wins, so the 500px cap is effectively ignored and it’s unclear which behavior is intended. Remove the duplicate and keep a single maximumSize value.

Copilot uses AI. Check for mistakes.
self.tableWidget.setSizePolicy(sizePolicy)
self.tableWidget.setMinimumSize(QtCore.QSize(225, 0))
self.tableWidget.setMaximumSize(QtCore.QSize(500, 16777215))
self.tableWidget.setMaximumSize(QtCore.QSize(16777215, 16777215))
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window1.py sets tableWidget maximum size twice in a row. The second call (16777215, 16777215) overrides the intended 500px width cap from the first call, so the new constraint has no effect. Remove the redundant call or keep only the desired maximum size.

Suggested change
self.tableWidget.setMaximumSize(QtCore.QSize(16777215, 16777215))

Copilot uses AI. Check for mistakes.
Comment on lines +224 to +229
def resizeEvent(self, a0: QResizeEvent | None) -> None:
if a0 is None:
return
event = a0
if self.scaleFactor and not self.scrollArea.widgetResizable():
self.zoomToFit()
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resizeEvent currently calls zoomToFit() whenever scaleFactor is set and scrollArea.widgetResizable() is false. This will also trigger after zoomTo1_1() (scaleFactor=1.0, widgetResizable=False) and after manual zoom in/out, so resizing the window will unexpectedly snap back to “fit”. Track an explicit “zoom-to-fit active” flag/state and only auto-fit on resize when that mode is enabled.

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +316
# Extract column names from first row if it looks like headers
first_row = rows[0]
if len(first_row) >= 2 and first_row[0] != "":
# Assume first row contains headers
for col_name in first_row[1:]:
if col_name and col_name not in self.column_names:
self.column_names.append(col_name)

self.listlocation = len(rows) - 1
for row_idx, rowdata in enumerate(rows):
if not rowdata:
continue
self.filelist.append(rowdata[0])
num_cols = len(self.column_names)
for column in range(num_cols):
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadCSV() treats the first row as column headers whenever first_row[0] != "", then still iterates over that row as data and appends rowdata[0] into filelist. For typical CSVs where the first cell is a filename/path, this mis-detects headers and causes the first file to be treated as header text, and/or attempts to label.load() a non-file string. Consider detecting headers explicitly (e.g., first cell equals "File") and, when headers are present, skip the header row when populating filelist/table rows.

Copilot uses AI. Check for mistakes.
Comment on lines +298 to +332
self.tableWidget.setRowCount(len(rows))
self.filelist = []
self.column_names = ["File"]

# Extract column names from first row if it looks like headers
first_row = rows[0]
if len(first_row) >= 2 and first_row[0] != "":
# Assume first row contains headers
for col_name in first_row[1:]:
if col_name and col_name not in self.column_names:
self.column_names.append(col_name)

self.listlocation = len(rows) - 1
for row_idx, rowdata in enumerate(rows):
if not rowdata:
continue
self.filelist.append(rowdata[0])
num_cols = len(self.column_names)
for column in range(num_cols):
val = rowdata[column] if len(rowdata) > column else ""
item = QTableWidgetItem(val)
self.tableWidget.setItem(row_idx, column, item)

# Find first unrated row
for i in range(1, min(len(self.column_names), len(rowdata))):
if (
i < len(rowdata)
and rowdata[i] == ""
and row_idx < self.listlocation
):
self.listlocation = row_idx
break

self.tableWidget.setHorizontalHeaderLabels(self.column_names)
self.tableWidget.resizeColumnsToContents()
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadCSV() builds self.column_names but never updates tableWidget’s column count to match. If the CSV contains more (or fewer) rating columns than the default 3, setItem() and setHorizontalHeaderLabels() will not align with the actual table structure (and may fail for out-of-range columns). Set tableWidget.setColumnCount(len(self.column_names)) (and adjust after header parsing) before filling cells/setting headers.

Copilot uses AI. Check for mistakes.
Comment on lines +422 to +448
def showColumnContextMenu(self, pos):
header = self.tableWidget.horizontalHeader()

if header.underMouse():
local_col = header.logicalIndexAt(pos)
header_pos = header.mapToGlobal(pos)
else:
local_col = self.tableWidget.columnAt(pos.x())
header_pos = header.mapToGlobal(
self.tableWidget.viewport().mapFromGlobal(pos)
)

if local_col == -1:
return

menu = QMenu(self)

if local_col > 0:
add_action = menu.addAction("Add Column") # type: ignore[assignment]
add_action.triggered.connect(self.addColumn) # type: ignore[union-attr]

rename_action = menu.addAction("Rename Column") # type: ignore[assignment]
rename_action.triggered.connect(self.renameColumn) # type: ignore[union-attr]

remove_action = menu.addAction("Remove Column") # type: ignore[assignment]
remove_action.triggered.connect(self.removeColumn) # type: ignore[union-attr]

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showColumnContextMenu() computes local_col based on the right-click position, but the triggered actions call renameColumn()/removeColumn(), which operate on tableWidget.currentColumn(). If the user right-clicks a header without selecting a cell in that column first, rename/remove may apply to the wrong column. Pass local_col through to the handlers (e.g., via a lambda/partial) or set the current column/selection before showing the menu.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +47
from PyQt5.QtCore import QSize, Qt, QEvent
from PyQt5.QtGui import (
QFont,
QPalette,
QPixmap,
QImage,
QMovie,
QKeyEvent,
QWheelEvent,
QResizeEvent,
)
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QHBoxLayout,
QGridLayout,
QLabel,
QPushButton,
QLineEdit,
QTextEdit,
QTableWidget,
QTableWidgetItem,
QHeaderView,
QScrollArea,
QSplitter,
QFileDialog,
QInputDialog,
QMessageBox,
QMenu,
QMenuBar,
QStatusBar,
QAbstractItemView,
QDialog,
QAction,
)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several newly listed Qt imports (e.g., QEvent, QFont, QMenuBar, QStatusBar) appear unused in this file, which makes future maintenance harder and can confuse static analysis. Consider removing unused imports or limiting the import list to the classes actually referenced in PyQC.py.

Copilot uses AI. Check for mistakes.

## Dependencies

- Python 3.14
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENTS.md states a dependency on “Python 3.14”, but pyproject.toml and uv.lock declare requires-python = ">=3.8". Please align the documentation with the actual supported Python versions (or update pyproject.toml/lock/config if 3.14 is truly required).

Suggested change
- Python 3.14
- Python >= 3.8

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
PyQC.py (1)

391-404: ⚠️ Potential issue | 🔴 Critical

Save duplicates the File column in CSV output.

At Line 397, rowdata is initialized with self.filelist[row], then Lines 398-403 iterate over ALL columns including column 0 (File). This writes the file path twice to each CSV row.

🐛 Proposed fix to skip column 0 when iterating
         if self.path:
             with open(self.path, "w", newline="") as f:
                 writer = csv.writer(f)
                 for row in range(self.tableWidget.rowCount()):
                     rowdata = [self.filelist[row]]
-                    for column in range(self.tableWidget.columnCount()):
+                    for column in range(1, self.tableWidget.columnCount()):
                         item = self.tableWidget.item(row, column)
                         if item is not None:
                             rowdata.append(item.text())
                         else:
                             rowdata.append("")
                     writer.writerow(rowdata)

Apply the same fix to Save method at Line 412.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PyQC.py` around lines 391 - 404, The CSV output duplicates the File column
because SaveAs initializes rowdata with self.filelist[row] then iterates over
all columns (including column 0); update the SaveAs method to skip column 0 when
building rowdata (start the inner loop from column 1) so the file path isn't
added twice, and apply the identical change to the Save method to keep behavior
consistent; locate methods SaveAs and Save and modify the loop over
self.tableWidget.columnCount() to begin at 1 instead of 0.
🧹 Nitpick comments (1)
window1.py (1)

12-12: Wildcard import is intentional per documented codegen workflow.

Static analysis flags this as potentially problematic (F403, F405), but per AGENTS.md, from image_widget import * is a required post-generation edit to integrate SaneDefaultsImageLabel. The current approach works but could be improved.

💡 Optional: Use explicit import for clarity
-from image_widget import *
+from image_widget import SaneDefaultsImageLabel

This would satisfy static analysis tools while maintaining the required functionality. Update AGENTS.md codegen instructions accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@window1.py` at line 12, Replace the wildcard import to an explicit import to
satisfy linters while preserving the required SaneDefaultsImageLabel
integration: change the current `from image_widget import *` usage so it
explicitly imports SaneDefaultsImageLabel (and any other needed symbols) in the
same location, and update AGENTS.md codegen instructions to instruct generated
code to use `from image_widget import SaneDefaultsImageLabel` instead of a
wildcard import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AGENTS.md`:
- Line 41: The README entry listing "Python 3.14" is incorrect (that version
isn't released); update the AGENTS.md line that currently reads "Python 3.14" to
the correct supported interpreter version (e.g., "Python 3.11" or "Python 3.12")
after verifying project compatibility, and ensure any related tooling/docs
mention the same version string to keep them consistent.

In `@PyQC.py`:
- Around line 428-432: The context-menu position calculation is wrong when
clicking the table body because pos is already in the table widget's coordinate
space; fix the computation in the else branch by converting pos to header/global
coordinates correctly: use the table widget's viewport.mapToGlobal or
header.mapToGlobal with a point derived from pos (not
viewport.mapFromGlobal(pos)). Update the logic around local_col, header_pos, and
the header.mapToGlobal/viewport mapping so header_pos is computed from a global
point obtained via self.tableWidget.viewport().mapToGlobal(pos) (or equivalent
correct conversion) before passing it to header.mapToGlobal.
- Around line 224-229: The resizeEvent currently calls zoomToFit whenever
scaleFactor is set and the scrollArea isn't widgetResizable, which overwrites
manual/1:1 zooms; introduce a zoomMode attribute (e.g., self.zoomMode = "fit" |
"manual") and update it wherever zoomToFit(), setScaleFactor(), or the
1:1/manual zoom actions occur, then change resizeEvent to call self.zoomToFit()
only when self.zoomMode == "fit" (leave behavior unchanged otherwise); ensure
zoomMode defaults to a sensible value and is updated in methods that change zoom
so manual zooms persist across window resizes.
- Around line 302-329: The first CSV header row (first_row) is being appended to
self.filelist and rendered into self.tableWidget after you populate
self.column_names; change the data-processing loop to skip the header row when
headers were detected (i.e., start iterating rows from index 1 instead of 0 when
first_row was used as headers), adjust how self.listlocation is
initialized/updated so it accounts for skipping the header, and ensure indexing
when creating QTableWidgetItem and checking unrated cells still references the
correct row indices (use the same header-detection flag or check first_row
presence to decide whether to start from 1 or 0 for the main for row_idx,
rowdata in enumerate(rows) loop that appends to self.filelist and sets table
items).

In `@window1.ui`:
- Around line 52-63: Remove the duplicate maximumSize property that overrides
the intended width cap: locate the two consecutive <property name="maximumSize">
blocks and delete the second block that sets width and height to 16777215 so the
first maximumSize with <width>500</width> remains effective; ensure only one
maximumSize property remains for the widget.

---

Outside diff comments:
In `@PyQC.py`:
- Around line 391-404: The CSV output duplicates the File column because SaveAs
initializes rowdata with self.filelist[row] then iterates over all columns
(including column 0); update the SaveAs method to skip column 0 when building
rowdata (start the inner loop from column 1) so the file path isn't added twice,
and apply the identical change to the Save method to keep behavior consistent;
locate methods SaveAs and Save and modify the loop over
self.tableWidget.columnCount() to begin at 1 instead of 0.

---

Nitpick comments:
In `@window1.py`:
- Line 12: Replace the wildcard import to an explicit import to satisfy linters
while preserving the required SaneDefaultsImageLabel integration: change the
current `from image_widget import *` usage so it explicitly imports
SaneDefaultsImageLabel (and any other needed symbols) in the same location, and
update AGENTS.md codegen instructions to instruct generated code to use `from
image_widget import SaneDefaultsImageLabel` instead of a wildcard import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57b7a83d-5f8e-4d5b-b659-8b39dd221d43

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6971c and 56664e6.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • AGENTS.md
  • PyQC.py
  • window1.py
  • window1.ui


## Dependencies

- Python 3.14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Verify the Python version requirement.

Python 3.14 is not yet released. This appears to be a typo—did you mean Python 3.11 or 3.12?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 41, The README entry listing "Python 3.14" is incorrect
(that version isn't released); update the AGENTS.md line that currently reads
"Python 3.14" to the correct supported interpreter version (e.g., "Python 3.11"
or "Python 3.12") after verifying project compatibility, and ensure any related
tooling/docs mention the same version string to keep them consistent.

Comment on lines +224 to +229
def resizeEvent(self, a0: QResizeEvent | None) -> None:
if a0 is None:
return
event = a0
if self.scaleFactor and not self.scrollArea.widgetResizable():
self.zoomToFit()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Resize behavior may not match user expectations.

The current logic triggers zoomToFit() on any resize when scaleFactor is set and widgetResizable() is False. This means after using "Zoom 1:1" or manual zoom in/out, resizing the window will override the zoom level and fit to window.

Consider tracking the zoom mode explicitly (e.g., self.zoomMode = "fit" vs "manual") so that only "Zoom to Fit" mode triggers auto-fit on resize, while 1:1 or manual zooms are preserved.

💡 Suggested approach
     self.scaleFactor = None
     self.insert_column = 1
     self.column_names = ["File", "QC_Raw", "QC_Pre"]
+    self.zoomMode = "fit"  # "fit" or "manual"
 def zoomTo1_1(self):
     if not self.label.content or self.label.content.size().width() == 0:
         return
     self.scrollArea.setWidgetResizable(False)
     self.scaleFactor = 1.0
+    self.zoomMode = "manual"
     self.label.resize(self.label.content.size())

 def zoomToFit(self):
     if not self.label.content or self.label.content.size().width() == 0:
         return
     content_size = self.label.content.size()
     viewport_size = self.scrollArea.viewport().size()
     scale_width = viewport_size.width() / content_size.width()
     scale_height = viewport_size.height() / content_size.height()
     self.scaleFactor = min(scale_width, scale_height)
     self.scrollArea.setWidgetResizable(False)
+    self.zoomMode = "fit"
     self.label.resize(self.scaleFactor * content_size)

 def resizeEvent(self, a0: QResizeEvent | None) -> None:
     if a0 is None:
         return
-    if self.scaleFactor and not self.scrollArea.widgetResizable():
+    if self.zoomMode == "fit" and self.scaleFactor:
         self.zoomToFit()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PyQC.py` around lines 224 - 229, The resizeEvent currently calls zoomToFit
whenever scaleFactor is set and the scrollArea isn't widgetResizable, which
overwrites manual/1:1 zooms; introduce a zoomMode attribute (e.g., self.zoomMode
= "fit" | "manual") and update it wherever zoomToFit(), setScaleFactor(), or the
1:1/manual zoom actions occur, then change resizeEvent to call self.zoomToFit()
only when self.zoomMode == "fit" (leave behavior unchanged otherwise); ensure
zoomMode defaults to a sensible value and is updated in methods that change zoom
so manual zooms persist across window resizes.

Comment on lines +302 to +329
# Extract column names from first row if it looks like headers
first_row = rows[0]
if len(first_row) >= 2 and first_row[0] != "":
# Assume first row contains headers
for col_name in first_row[1:]:
if col_name and col_name not in self.column_names:
self.column_names.append(col_name)

self.listlocation = len(rows) - 1
for row_idx, rowdata in enumerate(rows):
if not rowdata:
continue
self.filelist.append(rowdata[0])
num_cols = len(self.column_names)
for column in range(num_cols):
val = rowdata[column] if len(rowdata) > column else ""
item = QTableWidgetItem(val)
self.tableWidget.setItem(row_idx, column, item)

# Find first unrated row
for i in range(1, min(len(self.column_names), len(rowdata))):
if (
i < len(rowdata)
and rowdata[i] == ""
and row_idx < self.listlocation
):
self.listlocation = row_idx
break
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CSV header row is incorrectly treated as file data.

When the first row contains headers (detected at Line 304), the code extracts column names but still processes that row as data at Lines 311-329. This adds the header text (e.g., "File") to self.filelist and creates a table row for it, causing file load errors.

🐛 Proposed fix to skip header row when processing data
         # Extract column names from first row if it looks like headers
         first_row = rows[0]
-        if len(first_row) >= 2 and first_row[0] != "":
+        has_headers = len(first_row) >= 2 and not os.path.exists(first_row[0])
+        if has_headers:
             # Assume first row contains headers
             for col_name in first_row[1:]:
                 if col_name and col_name not in self.column_names:
                     self.column_names.append(col_name)
+            data_rows = rows[1:]
+        else:
+            data_rows = rows

-        self.listlocation = len(rows) - 1
-        for row_idx, rowdata in enumerate(rows):
+        self.tableWidget.setRowCount(len(data_rows))
+        self.listlocation = len(data_rows) - 1
+        for row_idx, rowdata in enumerate(data_rows):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PyQC.py` around lines 302 - 329, The first CSV header row (first_row) is
being appended to self.filelist and rendered into self.tableWidget after you
populate self.column_names; change the data-processing loop to skip the header
row when headers were detected (i.e., start iterating rows from index 1 instead
of 0 when first_row was used as headers), adjust how self.listlocation is
initialized/updated so it accounts for skipping the header, and ensure indexing
when creating QTableWidgetItem and checking unrated cells still references the
correct row indices (use the same header-detection flag or check first_row
presence to decide whether to start from 1 or 0 for the main for row_idx,
rowdata in enumerate(rows) loop that appends to self.filelist and sets table
items).

Comment on lines +428 to +432
else:
local_col = self.tableWidget.columnAt(pos.x())
header_pos = header.mapToGlobal(
self.tableWidget.viewport().mapFromGlobal(pos)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Context menu position calculation is incorrect for table body clicks.

When the click is on the table body (not header), pos is already relative to the table widget, but Line 431-432 uses self.tableWidget.viewport().mapFromGlobal(pos) which treats pos as a global coordinate. This will show the menu in the wrong location.

🐛 Proposed fix
         if header.underMouse():
             local_col = header.logicalIndexAt(pos)
             header_pos = header.mapToGlobal(pos)
         else:
             local_col = self.tableWidget.columnAt(pos.x())
-            header_pos = header.mapToGlobal(
-                self.tableWidget.viewport().mapFromGlobal(pos)
-            )
+            header_pos = self.tableWidget.mapToGlobal(pos)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else:
local_col = self.tableWidget.columnAt(pos.x())
header_pos = header.mapToGlobal(
self.tableWidget.viewport().mapFromGlobal(pos)
)
else:
local_col = self.tableWidget.columnAt(pos.x())
header_pos = self.tableWidget.mapToGlobal(pos)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PyQC.py` around lines 428 - 432, The context-menu position calculation is
wrong when clicking the table body because pos is already in the table widget's
coordinate space; fix the computation in the else branch by converting pos to
header/global coordinates correctly: use the table widget's viewport.mapToGlobal
or header.mapToGlobal with a point derived from pos (not
viewport.mapFromGlobal(pos)). Update the logic around local_col, header_pos, and
the header.mapToGlobal/viewport mapping so header_pos is computed from a global
point obtained via self.tableWidget.viewport().mapToGlobal(pos) (or equivalent
correct conversion) before passing it to header.mapToGlobal.

Comment on lines +52 to +63
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate maximumSize property; second definition overrides the first.

Lines 52-57 set maximumSize to width 500, but Lines 58-63 immediately override it with an unbounded size (16777215). The width constraint of 500 will not be applied.

If the intent is to cap the table width at 500px, remove the second maximumSize definition.

🐛 Proposed fix to remove duplicate property
        <property name="maximumSize">
         <size>
          <width>500</width>
          <height>16777215</height>
         </size>
        </property>
-       <property name="maximumSize">
-        <size>
-         <width>16777215</width>
-         <height>16777215</height>
-        </size>
-       </property>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@window1.ui` around lines 52 - 63, Remove the duplicate maximumSize property
that overrides the intended width cap: locate the two consecutive <property
name="maximumSize"> blocks and delete the second block that sets width and
height to 16777215 so the first maximumSize with <width>500</width> remains
effective; ensure only one maximumSize property remains for the widget.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants