Co4E step config as collapsible sections; tighter Kanban lanes; Output panel fills its column
CI / test (pull_request) Canceled after 0s

- co4e_config_panel: StepConfigPanel reorganized into four collapsible,
  borderless sections (Co ban / Model & Quyen / Skills & Tep / Agent song
  song) instead of one long form. Agent song song is its own section,
  shown only for parallel-variant steps. Fixes a QVBoxLayout without a
  trailing stretch that was handing every section an equal share of the
  scroll area's leftover height instead of sizing to content.
- schedule_task_tab / theme: Kanban board lane gutters and per-lane
  margins trimmed so the seven lanes read their card content more
  clearly on a laptop-width window.
- chat_panel / widgets: the Output Files list in Cowork/Code no longer
  stops at a fixed height with empty panel below it - it now fills the
  column down to the composer.
- composer: Attach/Send/Stop buttons match the input box's height and
  align to its top/bottom edge instead of floating centered beside it.
- app.py: the sidebar's "All projects" link reads as a link (italic,
  accent color) instead of a plain row.
- i18n: labels for the three renamed Co4E section headers (VI/EN/JA).
This commit is contained in:
Hiep Ha Van
2026-08-20 01:09:01 +09:00
parent 2ea20a8548
commit d68d91ae05
8 changed files with 202 additions and 32 deletions
+7 -2
View File
@@ -7,7 +7,7 @@ from pathlib import Path
from typing import List from typing import List
from PySide6.QtCore import Qt, QTimer from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QGuiApplication, QIcon from PySide6.QtGui import QColor, QGuiApplication, QIcon
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QStyledItemDelegate, QStyledItemDelegate,
QApplication, QComboBox, QHBoxLayout, QLabel, QMainWindow, QMenu, QApplication, QComboBox, QHBoxLayout, QLabel, QMainWindow, QMenu,
@@ -747,9 +747,14 @@ class MainWindow(QMainWindow):
it = QTreeWidgetItem([tr("sidebar.empty")]) it = QTreeWidgetItem([tr("sidebar.empty")])
it.setDisabled(True) it.setDisabled(True)
tree.addTopLevelItem(it) tree.addTopLevelItem(it)
# The way back to everything the rail cannot show. # The way back to everything the rail cannot show — styled as a link
# (italic, accent-colored) so it reads as "go elsewhere", not another row.
more = QTreeWidgetItem([tr("app.nav.all_projects")]) more = QTreeWidgetItem([tr("app.nav.all_projects")])
more.setData(0, Qt.UserRole, {"all": True}) more.setData(0, Qt.UserRole, {"all": True})
more_font = more.font(0)
more_font.setItalic(True)
more.setFont(0, more_font)
more.setForeground(0, QColor(current_palette().accent))
tree.addTopLevelItem(more) tree.addTopLevelItem(more)
tree.blockSignals(blocked) tree.blockSignals(blocked)
self.nav_recents_hdr.setVisible(not self._nav_collapsed) self.nav_recents_hdr.setVisible(not self._nav_collapsed)
+3
View File
@@ -2765,6 +2765,9 @@ STRINGS: Dict[str, Dict[str, str]] = {
"ja": "フローとチャット — /agent:<name> または /skill:<name>", "ja": "フローとチャット — /agent:<name> または /skill:<name>",
"vi": "Chat với flow — dùng /agent:<name> hoặc /skill:<name>"}, "vi": "Chat với flow — dùng /agent:<name> hoặc /skill:<name>"},
"co4e.send": {"en": "Send", "ja": "送信", "vi": "Gửi"}, "co4e.send": {"en": "Send", "ja": "送信", "vi": "Gửi"},
"co4e.tab_basic": {"en": "Basic", "ja": "基本", "vi": "Cơ bản"},
"co4e.tab_model_perm": {"en": "Model & Permission", "ja": "モデルと権限", "vi": "Model & Quyền"},
"co4e.tab_skills_files": {"en": "Skills & Files", "ja": "スキルとファイル", "vi": "Skills & Tệp"},
"co4e.f_label": {"en": "Label", "ja": "ラベル", "vi": "Nhãn"}, "co4e.f_label": {"en": "Label", "ja": "ラベル", "vi": "Nhãn"},
"co4e.f_role": {"en": "Role", "ja": "ロール", "vi": "Vai trò"}, "co4e.f_role": {"en": "Role", "ja": "ロール", "vi": "Vai trò"},
"co4e.f_name": {"en": "Name", "ja": "名前", "vi": "Tên"}, "co4e.f_name": {"en": "Name", "ja": "名前", "vi": "Tên"},
+4
View File
@@ -641,6 +641,10 @@ QTreeView::item:selected, QListView::item:selected, QTableView::item:selected {
cell on top of the selection tint above — visible as a stray light border cell on top of the selection tint above — visible as a stray light border
on a click. The selection tint already marks "current row"; drop the rect. */ on a click. The selection tint already marks "current row"; drop the rect. */
QTreeView::item:focus, QListView::item:focus, QTableView::item:focus { outline: none; border: none; } QTreeView::item:focus, QListView::item:focus, QTableView::item:focus { outline: none; border: none; }
/* Kanban lanes (Schedule Task): seven lanes share the board width, so a card's
own inset competes with the other six for space the same way the inter-lane
gap did — trimmed to match. */
QListWidget#kanbanLane::item { padding: 3px 2px; }
QHeaderView::section { QHeaderView::section {
background: $bg; color: $text_muted; border: none; background: $bg; color: $text_muted; border: none;
border-bottom: 1px solid $border; padding: 7px 6px; font-weight: 600; border-bottom: 1px solid $border; padding: 7px 6px; font-weight: 600;
+6 -3
View File
@@ -214,7 +214,11 @@ class ChatPanel(QWidget):
# Right sidebar: Output files only (see below — Input is tracked but # Right sidebar: Output files only (see below — Input is tracked but
# not shown). # not shown).
self.input_section = CollapsibleSection(tr("widgets.input_files")) self.input_section = CollapsibleSection(tr("widgets.input_files"))
self.output_section = CollapsibleSection(tr("widgets.output_files").upper()) # No cap: this section owns the whole right panel (its header is
# hoisted into io_hdr below), so the list should fill the space down
# to the composer instead of stopping at a fixed height with empty
# panel below it.
self.output_section = CollapsibleSection(tr("widgets.output_files").upper(), max_height=None)
# Input files are NOT shown in Cowork's UI anymore — but they're still # Input files are NOT shown in Cowork's UI anymore — but they're still
# fully tracked (add/remove/paths()) exactly as before, since that list # fully tracked (add/remove/paths()) exactly as before, since that list
# is what gets written into the conversation's own "inputs" field on # is what gets written into the conversation's own "inputs" field on
@@ -273,8 +277,7 @@ class ChatPanel(QWidget):
bl = QVBoxLayout(bl_host) bl = QVBoxLayout(bl_host)
bl.setContentsMargins(0, 0, 0, 0) bl.setContentsMargins(0, 0, 0, 0)
bl.setSpacing(4) bl.setSpacing(4)
bl.addWidget(self.output_section) # Output only — Input is tracked but hidden bl.addWidget(self.output_section, 1) # Output only — Input is tracked but hidden; fills down to the composer
bl.addStretch(1)
iol.addWidget(bl_host, 1) iol.addWidget(bl_host, 1)
# Collapsing shrinks the panel to a thin clickable line (not hidden). # Collapsing shrinks the panel to a thin clickable line (not hidden).
+155 -23
View File
@@ -11,18 +11,124 @@ from __future__ import annotations
from typing import List, Optional from typing import List, Optional
from PySide6.QtCore import Qt, Signal from PySide6.QtCore import Qt, QEasingCurve, QPropertyAnimation, Signal
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QCheckBox, QComboBox, QFormLayout, QHBoxLayout, QInputDialog, QLabel, QCheckBox, QComboBox, QFormLayout, QHBoxLayout, QInputDialog,
QLineEdit, QListWidget, QListWidgetItem, QPlainTextEdit, QPushButton, QLabel, QLineEdit, QListWidget, QListWidgetItem, QPlainTextEdit,
QScrollArea, QSpinBox, QVBoxLayout, QWidget, QPushButton, QScrollArea, QSpinBox, QVBoxLayout, QWidget,
) )
from ..config import PROVIDER_LABELS from ..config import PROVIDER_LABELS
from ..core.co4e import PERMISSION_PRESETS, Step, SubAgent from ..core.co4e import PERMISSION_PRESETS, Step, SubAgent
from ..i18n import tr from ..i18n import tr
from ..theme import current_palette
from .icons import icon, icon_picker_combo from .icons import icon, icon_picker_combo
_SECTION_ANIM_MS = 180
class _SectionHeader(QLabel):
"""A clickable label — a QPushButton's own style chrome (border, native
button margin, focus rect) always leaves a taller minimum height than a
plain label, even once its QSS padding is zeroed out, so the header that
needs to sit tight against its neighbours is a label, not a button."""
clicked = Signal()
def mousePressEvent(self, event) -> None: # noqa: N802
if event.button() == Qt.LeftButton:
self.clicked.emit()
super().mousePressEvent(event)
def showEvent(self, event) -> None: # noqa: N802
# fontMetrics() at construction time (before this label is ever part
# of a shown top-level window) reflects the QSS font-size only if the
# style has fully polished by then — on the very FIRST paint of the
# Co4E screen it sometimes hasn't, so the fixed height computed in
# _add_section is briefly wrong (too tall) until something else
# triggers a relayout. Recomputing here, every time the label
# actually becomes visible, means the first paint is never stale.
self.setFixedHeight(self.fontMetrics().height())
super().showEvent(event)
def _add_section(outer: QVBoxLayout, title: str) -> tuple[QFormLayout, QWidget]:
"""One group of fields, collapsed to just its heading by default and
independently expandable, so a long step config reads as a short list of
group names until you open the one you need. Deliberately bare — no card
border/background/box — the ▶/▼ marker and the heading text are the only
things separating one group from the next; opening one never closes
another (not an accordion, not a tab bar). Returns ``(form, card)``: add
the group's rows to ``form``; ``card`` is the whole section (header +
body) — hide it to remove the group entirely (e.g. for a section that
only applies to some steps), rather than hiding individual rows inside
an always-visible header."""
p = current_palette()
card = QWidget()
card_lay = QVBoxLayout(card)
card_lay.setContentsMargins(0, 0, 0, 0)
card_lay.setSpacing(0)
header = _SectionHeader()
header.setCursor(Qt.PointingHandCursor)
header.setStyleSheet(f"font-weight:400; font-size:13px; color:{p.text}; padding:0; margin:0;")
header.setContentsMargins(0, 0, 0, 0)
# QSS font-size only lands on the widget's actual QFont (and therefore
# its fontMetrics()) once the style sheet is polished — ensurePolished()
# forces that now, so the fixed height below is computed from the 12px
# font just set above, not the default one this label was constructed
# with. A label's natural sizeHint still reserves font leading above/
# below the glyphs on top of the (now zeroed) QSS padding — pinning the
# height to the text's actual cap-to-baseline span is what closes that
# last gap without clipping the ▶ glyph, the title, or Vietnamese
# diacritics.
header.ensurePolished()
header.setFixedHeight(header.fontMetrics().height())
header.setText(f"▶ {title}")
card_lay.addWidget(header)
body = QWidget()
body.setVisible(False)
body.setMaximumHeight(0)
form = QFormLayout(body)
form.setContentsMargins(0, 6, 0, 0)
card_lay.addWidget(body)
anim = QPropertyAnimation(body, b"maximumHeight", body)
anim.setDuration(_SECTION_ANIM_MS)
anim.setEasingCurve(QEasingCurve.InOutCubic)
is_open = False
def _on_finished() -> None:
if is_open:
# Uncapped once open, so switching to a step whose fields make
# this section taller/shorter (e.g. a parallel node's sub-agent
# list appearing) is never clipped by the height this animation
# last landed on.
body.setMaximumHeight(16_777_215)
else:
body.setVisible(False)
anim.finished.connect(_on_finished)
def _toggle() -> None:
nonlocal is_open
is_open = not is_open
header.setText(f"{'▼' if is_open else '▶'} {title}")
anim.stop()
if is_open:
body.setVisible(True)
anim.setStartValue(body.height())
anim.setEndValue(body.sizeHint().height())
else:
anim.setStartValue(body.height())
anim.setEndValue(0)
anim.start()
header.clicked.connect(_toggle)
outer.addWidget(card)
return form, card
class StepConfigPanel(QScrollArea): class StepConfigPanel(QScrollArea):
changed = Signal() # any field edited → repaint node + autosave changed = Signal() # any field edited → repaint node + autosave
@@ -39,7 +145,16 @@ class StepConfigPanel(QScrollArea):
self.setWidgetResizable(True) self.setWidgetResizable(True)
host = QWidget() host = QWidget()
self.setWidget(host) self.setWidget(host)
form = QFormLayout(host) outer = QVBoxLayout(host)
outer.setSpacing(1)
# Grouped sections stacked on one scrolling page — same fields as
# before, grouped by what they're for: identity, execution
# (model/permission), and the extra resources fed to the step
# (skills/files/sub-agents). No tabs/accordion: every group's border
# and heading are what separate it from its neighbours, and all three
# are on screen (or one scroll away) at once.
form, _basic_card = _add_section(outer, tr("co4e.tab_basic"))
self.label_edit = QLineEdit() self.label_edit = QLineEdit()
self.label_edit.textChanged.connect(self._on_edit) self.label_edit.textChanged.connect(self._on_edit)
@@ -80,6 +195,8 @@ class StepConfigPanel(QScrollArea):
self.context_edit.textChanged.connect(self._on_edit) self.context_edit.textChanged.connect(self._on_edit)
form.addRow(tr("co4e.f_context"), self.context_edit) form.addRow(tr("co4e.f_context"), self.context_edit)
form2, _model_card = _add_section(outer, tr("co4e.tab_model_perm"))
model_row = QHBoxLayout() model_row = QHBoxLayout()
self.model_combo = QComboBox() self.model_combo = QComboBox()
self.model_combo.setEditable(True) self.model_combo.setEditable(True)
@@ -92,13 +209,13 @@ class StepConfigPanel(QScrollArea):
model_row.addWidget(self.model_combo, 1) model_row.addWidget(self.model_combo, 1)
model_row.addWidget(self.load_models_btn) model_row.addWidget(self.load_models_btn)
mrow = QWidget(); mrow.setLayout(model_row) mrow = QWidget(); mrow.setLayout(model_row)
form.addRow(tr("co4e.f_model"), mrow) form2.addRow(tr("co4e.f_model"), mrow)
self.perm_combo = QComboBox() self.perm_combo = QComboBox()
for preset in PERMISSION_PRESETS: for preset in PERMISSION_PRESETS:
self.perm_combo.addItem(tr(f"co4e.perm.{preset}"), preset) self.perm_combo.addItem(tr(f"co4e.perm.{preset}"), preset)
self.perm_combo.currentIndexChanged.connect(self._on_edit) self.perm_combo.currentIndexChanged.connect(self._on_edit)
form.addRow(tr("co4e.f_permission"), self.perm_combo) form2.addRow(tr("co4e.f_permission"), self.perm_combo)
verify_row = QHBoxLayout() verify_row = QHBoxLayout()
self.verify_chk = QCheckBox(tr("co4e.f_self_verify")) self.verify_chk = QCheckBox(tr("co4e.f_self_verify"))
@@ -111,13 +228,15 @@ class StepConfigPanel(QScrollArea):
verify_row.addWidget(self.rounds_spin) verify_row.addWidget(self.rounds_spin)
verify_row.addStretch(1) verify_row.addStretch(1)
vrow = QWidget(); vrow.setLayout(verify_row) vrow = QWidget(); vrow.setLayout(verify_row)
form.addRow("", vrow) form2.addRow("", vrow)
form3, _skills_card = _add_section(outer, tr("co4e.tab_skills_files"))
# Skills checklist (registry skills) # Skills checklist (registry skills)
self.skills_list = QListWidget() self.skills_list = QListWidget()
self.skills_list.setMaximumHeight(110) self.skills_list.setMaximumHeight(110)
self.skills_list.itemChanged.connect(self._on_edit) self.skills_list.itemChanged.connect(self._on_edit)
form.addRow(tr("co4e.f_skills"), self.skills_list) form3.addRow(tr("co4e.f_skills"), self.skills_list)
# Attachments — files whose extracted text is fed to this step at run time. # Attachments — files whose extracted text is fed to this step at run time.
self.attach_list = QListWidget() self.attach_list = QListWidget()
@@ -133,11 +252,15 @@ class StepConfigPanel(QScrollArea):
att_btns.addWidget(self.attach_del_btn) att_btns.addWidget(self.attach_del_btn)
att_btns.addStretch(1) att_btns.addStretch(1)
abtn = QWidget(); abtn.setLayout(att_btns) abtn = QWidget(); abtn.setLayout(att_btns)
form.addRow(tr("co4e.f_attachments"), self.attach_list) form3.addRow(tr("co4e.f_attachments"), self.attach_list)
form.addRow("", abtn) form3.addRow("", abtn)
# Parallel sub-agents (only shown for parallel nodes) # Parallel sub-agents get their OWN section — same header style as
self.parallel_label = QLabel(tr("co4e.f_subagents")) # Cơ bản/Model & Quyền/Skills & Tệp — rather than a row buried inside
# Skills & Tệp, since it's really a distinct group, just one that
# only applies to parallel-variant steps. load_step() hides the whole
# card for a non-parallel step (see is_par below).
form4, self._parallel_card = _add_section(outer, tr("co4e.f_subagents"))
self.sub_list = QListWidget() self.sub_list = QListWidget()
self.sub_list.setMaximumHeight(90) self.sub_list.setMaximumHeight(90)
self.sub_list.itemDoubleClicked.connect(self._edit_subagent) # re-pick agent self.sub_list.itemDoubleClicked.connect(self._edit_subagent) # re-pick agent
@@ -152,10 +275,11 @@ class StepConfigPanel(QScrollArea):
sub_btns.addWidget(self.sub_del_btn) sub_btns.addWidget(self.sub_del_btn)
sub_btns.addStretch(1) sub_btns.addStretch(1)
sbtn = QWidget(); sbtn.setLayout(sub_btns) sbtn = QWidget(); sbtn.setLayout(sub_btns)
form.addRow(self.parallel_label, self.sub_list) form4.addRow(self.sub_list)
form.addRow("", sbtn) form4.addRow("", sbtn)
# Footer actions — one compact row (Run · Run from here · Delete). # Footer actions — one compact row (Run · Run from here · Delete),
# kept below every section, not inside one of the cards.
self.run_btn = QPushButton(tr("co4e.run")) self.run_btn = QPushButton(tr("co4e.run"))
self.run_btn.setIcon(icon("play")) self.run_btn.setIcon(icon("play"))
self.run_btn.setToolTip(tr("co4e.run_this_step")) self.run_btn.setToolTip(tr("co4e.run_this_step"))
@@ -170,12 +294,21 @@ class StepConfigPanel(QScrollArea):
self.del_btn.setFixedWidth(38) self.del_btn.setFixedWidth(38)
self.del_btn.clicked.connect(lambda: self.delete_node.emit(self._node_id)) self.del_btn.clicked.connect(lambda: self.delete_node.emit(self._node_id))
foot = QHBoxLayout() foot = QHBoxLayout()
foot.setContentsMargins(0, 0, 0, 0)
foot.addWidget(self.run_btn, 1) foot.addWidget(self.run_btn, 1)
foot.addWidget(self.run_from_btn, 1) foot.addWidget(self.run_from_btn, 1)
foot.addWidget(self.del_btn) foot.addWidget(self.del_btn)
foot_w = QWidget(); foot_w.setLayout(foot) foot_w = QWidget(); foot_w.setLayout(foot)
form.addRow("", foot_w) outer.addWidget(foot_w)
# Without this, QVBoxLayout hands every child widget an EQUAL share of
# whatever extra height the scroll area's viewport has beyond the
# content's own sizeHint (setWidgetResizable(True) stretches `host` to
# fill it) — each collapsed header's card was measuring a true
# sizeHint of ~17px but rendering over 100px taller, and no amount of
# margin/padding/spacing on the header itself could touch that: the
# surplus was being spent on the cards, not around them. One trailing
# stretch absorbs all of it instead, so every section (and the
# footer) renders at exactly its own natural height.
outer.addStretch(1)
self.setEnabled(False) self.setEnabled(False)
@@ -209,12 +342,11 @@ class StepConfigPanel(QScrollArea):
item = QListWidgetItem(_P(path).name) item = QListWidgetItem(_P(path).name)
item.setToolTip(path) item.setToolTip(path)
self.attach_list.addItem(item) self.attach_list.addItem(item)
# parallel sub-agents # parallel sub-agents — the whole "Agent song song" section only
# applies to parallel-variant steps, so the entire card (header
# included) is hidden for any other step, not just its rows.
is_par = step.is_parallel is_par = step.is_parallel
self.parallel_label.setVisible(is_par) self._parallel_card.setVisible(is_par)
self.sub_list.setVisible(is_par)
self.sub_add_btn.setVisible(is_par)
self.sub_del_btn.setVisible(is_par)
self.sub_list.clear() self.sub_list.clear()
if is_par: if is_par:
for sub in step.sub_agents: for sub in step.sub_agents:
+5
View File
@@ -452,7 +452,12 @@ class Composer(QWidget):
self.stop_btn.setObjectName("danger") self.stop_btn.setObjectName("danger")
self.stop_btn.setVisible(False) self.stop_btn.setVisible(False)
self.stop_btn.clicked.connect(self.stop_requested.emit) self.stop_btn.clicked.connect(self.stop_requested.emit)
# Attach pinned to the input's top edge, Send (and Stop, once a turn
# is running) pinned to its bottom edge — the gap between them is
# absorbed by this stretch instead of splitting evenly above/below
# the whole button column, which is what centering it did before.
btns.addWidget(self.attach_btn) btns.addWidget(self.attach_btn)
btns.addStretch(1)
btns.addWidget(self.send_btn) btns.addWidget(self.send_btn)
btns.addWidget(self.stop_btn) btns.addWidget(self.stop_btn)
row.addLayout(btns) row.addLayout(btns)
+12 -1
View File
@@ -133,14 +133,25 @@ class ScheduleTaskTab(QWidget):
board = QWidget() board = QWidget()
scroll.setWidget(board) scroll.setWidget(board)
cols = QHBoxLayout(board) cols = QHBoxLayout(board)
cols.setSpacing(8) # Gutters wide enough to read as a break between lanes without eating
# too much of the seven-way split — they still share the board equally
# (see _fit_lanes below), so a wider gutter narrows every lane by the
# same share automatically; nothing else to compute here.
cols.setSpacing(2)
self.columns: Dict[str, _KanbanColumn] = {} self.columns: Dict[str, _KanbanColumn] = {}
self.column_headers: Dict[str, QLabel] = {} self.column_headers: Dict[str, QLabel] = {}
for status in STATUSES: for status in STATUSES:
box = QVBoxLayout() box = QVBoxLayout()
# The per-lane holder's own margins were the style's default
# (~9px a side) on top of the inter-column gap — with seven lanes
# that outweighs the gap itself. Zero it out and let the lane's
# header/list fill the width _fit_lanes() hands them.
box.setContentsMargins(0, 0, 0, 0)
box.setSpacing(2)
head = QLabel() head = QLabel()
head.setStyleSheet("font-weight:600;") head.setStyleSheet("font-weight:600;")
col = _KanbanColumn(status) col = _KanbanColumn(status)
col.setObjectName("kanbanLane")
col.task_dropped.connect(self._on_task_dropped) col.task_dropped.connect(self._on_task_dropped)
col.itemDoubleClicked.connect(self._on_double_click) col.itemDoubleClicked.connect(self._on_double_click)
col.setContextMenuPolicy(Qt.CustomContextMenu) col.setContextMenuPolicy(Qt.CustomContextMenu)
+10 -3
View File
@@ -739,7 +739,11 @@ class CollapsibleSection(QWidget):
activated = Signal(str) # emits the path of a clicked item activated = Signal(str) # emits the path of a clicked item
def __init__(self, title: str, max_height: int = 130): def __init__(self, title: str, max_height: int | None = 130):
"""``max_height`` caps the list so it scrolls instead of growing
(the default, e.g. for a section sharing space with siblings).
``None`` instead lets it expand to fill whatever room its parent
layout hands it — for a section that owns the whole panel."""
super().__init__() super().__init__()
self._title = title self._title = title
self._paths: list[str] = [] self._paths: list[str] = []
@@ -756,11 +760,14 @@ class CollapsibleSection(QWidget):
lay.addWidget(self.header) lay.addWidget(self.header)
self.list = QListWidget() self.list = QListWidget()
self.list.setMaximumHeight(max_height) # scrolls when longer if max_height is not None:
self.list.setMaximumHeight(max_height) # scrolls when longer
else:
self.list.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
self.list.setVisible(False) self.list.setVisible(False)
self.list.itemActivated.connect(self._emit) self.list.itemActivated.connect(self._emit)
self.list.itemClicked.connect(self._emit) self.list.itemClicked.connect(self._emit)
lay.addWidget(self.list) lay.addWidget(self.list, 1 if max_height is None else 0)
self.setVisible(False) self.setVisible(False)
self._update_header() self._update_header()