|
|
@@ -0,0 +1,491 @@
|
|
|
+"""
|
|
|
+Main Window for BMAD Novel Translator.
|
|
|
+
|
|
|
+Implements the primary application window with menu bar, toolbar,
|
|
|
+and central workspace.
|
|
|
+"""
|
|
|
+
|
|
|
+from PyQt6.QtWidgets import (
|
|
|
+ QMainWindow,
|
|
|
+ QWidget,
|
|
|
+ QVBoxLayout,
|
|
|
+ QHBoxLayout,
|
|
|
+ QMenuBar,
|
|
|
+ QToolBar,
|
|
|
+ QStatusBar,
|
|
|
+ QSplitter,
|
|
|
+ QLabel,
|
|
|
+ QPushButton,
|
|
|
+ QTableView,
|
|
|
+ QHeaderView,
|
|
|
+)
|
|
|
+from PyQt6.QtCore import Qt, QSize, pyqtSignal, QTimer
|
|
|
+from PyQt6.QtGui import QAction, QIcon, QKeySequence, QFont
|
|
|
+
|
|
|
+from .models import FileItem, TranslationTask, FileStatus, TaskStatus
|
|
|
+from .file_list_model import FileListModel
|
|
|
+
|
|
|
+
|
|
|
+class MainWindow(QMainWindow):
|
|
|
+ """
|
|
|
+ Main application window.
|
|
|
+
|
|
|
+ Features:
|
|
|
+ - Menu bar with File, Edit, View, Tools, Help menus
|
|
|
+ - Toolbar with common actions
|
|
|
+ - Central workspace with file list and task controls
|
|
|
+ - Status bar showing current status and version
|
|
|
+ """
|
|
|
+
|
|
|
+ # Signals
|
|
|
+ file_added = pyqtSignal(object) # FileItem
|
|
|
+ file_removed = pyqtSignal(object) # FileItem
|
|
|
+ translation_started = pyqtSignal()
|
|
|
+ translation_paused = pyqtSignal()
|
|
|
+ translation_cancelled = pyqtSignal()
|
|
|
+ settings_requested = pyqtSignal()
|
|
|
+ about_requested = pyqtSignal()
|
|
|
+
|
|
|
+ # Constants
|
|
|
+ DEFAULT_WIDTH = 1200
|
|
|
+ DEFAULT_HEIGHT = 800
|
|
|
+ MINIMUM_WIDTH = 900
|
|
|
+ MINIMUM_HEIGHT = 600
|
|
|
+ APPLICATION_TITLE = "BMAD Novel Translator"
|
|
|
+ APPLICATION_VERSION = "0.1.0"
|
|
|
+
|
|
|
+ def __init__(self, parent: QWidget | None = None) -> None:
|
|
|
+ """Initialize the main window."""
|
|
|
+ super().__init__(parent)
|
|
|
+
|
|
|
+ self._file_items: list[FileItem] = []
|
|
|
+ self._current_task: TranslationTask | None = None
|
|
|
+
|
|
|
+ self._setup_ui()
|
|
|
+ self._create_menu_bar()
|
|
|
+ self._create_toolbar()
|
|
|
+ self._create_status_bar()
|
|
|
+ self._connect_signals()
|
|
|
+
|
|
|
+ def _setup_ui(self) -> None:
|
|
|
+ """Set up the main window UI components."""
|
|
|
+ self.setWindowTitle(self.APPLICATION_TITLE)
|
|
|
+ self.resize(self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
|
|
+ self.setMinimumSize(self.MINIMUM_WIDTH, self.MINIMUM_HEIGHT)
|
|
|
+
|
|
|
+ # Create central widget
|
|
|
+ central_widget = QWidget()
|
|
|
+ self.setCentralWidget(central_widget)
|
|
|
+
|
|
|
+ # Main layout
|
|
|
+ main_layout = QVBoxLayout(central_widget)
|
|
|
+ main_layout.setContentsMargins(8, 8, 8, 8)
|
|
|
+ main_layout.setSpacing(8)
|
|
|
+
|
|
|
+ # Create splitter for resizable panes
|
|
|
+ splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
|
+ main_layout.addWidget(splitter)
|
|
|
+
|
|
|
+ # Left panel - File list
|
|
|
+ left_panel = self._create_file_list_panel()
|
|
|
+ splitter.addWidget(left_panel)
|
|
|
+
|
|
|
+ # Right panel - Task controls and progress
|
|
|
+ right_panel = self._create_task_panel()
|
|
|
+ splitter.addWidget(right_panel)
|
|
|
+
|
|
|
+ # Set initial splitter sizes (60% left, 40% right)
|
|
|
+ splitter.setSizes([600, 400])
|
|
|
+
|
|
|
+ def _create_file_list_panel(self) -> QWidget:
|
|
|
+ """Create the file list panel."""
|
|
|
+ panel = QWidget()
|
|
|
+ layout = QVBoxLayout(panel)
|
|
|
+ layout.setContentsMargins(0, 0, 0, 0)
|
|
|
+ layout.setSpacing(4)
|
|
|
+
|
|
|
+ # Header
|
|
|
+ header_layout = QHBoxLayout()
|
|
|
+ title_label = QLabel("Files")
|
|
|
+ title_label.setFont(QFont("", 12, QFont.Weight.Bold))
|
|
|
+ header_layout.addWidget(title_label)
|
|
|
+ header_layout.addStretch()
|
|
|
+
|
|
|
+ # Add file buttons
|
|
|
+ self._add_file_btn = QPushButton("Add Files")
|
|
|
+ self._add_folder_btn = QPushButton("Add Folder")
|
|
|
+ self._remove_file_btn = QPushButton("Remove")
|
|
|
+ self._clear_all_btn = QPushButton("Clear All")
|
|
|
+
|
|
|
+ header_layout.addWidget(self._add_file_btn)
|
|
|
+ header_layout.addWidget(self._add_folder_btn)
|
|
|
+ header_layout.addWidget(self._remove_file_btn)
|
|
|
+ header_layout.addWidget(self._clear_all_btn)
|
|
|
+ layout.addLayout(header_layout)
|
|
|
+
|
|
|
+ # File table
|
|
|
+ self._file_table = QTableView()
|
|
|
+ self._file_model = FileListModel(self._file_items)
|
|
|
+ self._file_table.setModel(self._file_model)
|
|
|
+
|
|
|
+ # Configure table
|
|
|
+ self._file_table.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
|
|
|
+ self._file_table.setSelectionMode(QTableView.SelectionMode.ExtendedSelection)
|
|
|
+ self._file_table.setAlternatingRowColors(True)
|
|
|
+ self._file_table.horizontalHeader().setStretchLastSection(True)
|
|
|
+ self._file_table.horizontalHeader().setSectionResizeMode(
|
|
|
+ 0, QHeaderView.ResizeMode.Stretch
|
|
|
+ )
|
|
|
+
|
|
|
+ layout.addWidget(self._file_table)
|
|
|
+
|
|
|
+ return panel
|
|
|
+
|
|
|
+ def _create_task_panel(self) -> QWidget:
|
|
|
+ """Create the task control and progress panel."""
|
|
|
+ panel = QWidget()
|
|
|
+ layout = QVBoxLayout(panel)
|
|
|
+ layout.setContentsMargins(0, 0, 0, 0)
|
|
|
+ layout.setSpacing(8)
|
|
|
+
|
|
|
+ # Task controls section
|
|
|
+ controls_group = QWidget()
|
|
|
+ controls_layout = QVBoxLayout(controls_group)
|
|
|
+ controls_layout.setSpacing(8)
|
|
|
+
|
|
|
+ # Control buttons
|
|
|
+ buttons_layout = QHBoxLayout()
|
|
|
+ self._start_btn = QPushButton("Start Translation")
|
|
|
+ self._start_btn.setEnabled(False)
|
|
|
+ self._pause_btn = QPushButton("Pause")
|
|
|
+ self._pause_btn.setEnabled(False)
|
|
|
+ self._cancel_btn = QPushButton("Cancel")
|
|
|
+ self._cancel_btn.setEnabled(False)
|
|
|
+
|
|
|
+ self._start_btn.setMinimumHeight(40)
|
|
|
+ self._pause_btn.setMinimumHeight(40)
|
|
|
+ self._cancel_btn.setMinimumHeight(40)
|
|
|
+
|
|
|
+ buttons_layout.addWidget(self._start_btn)
|
|
|
+ buttons_layout.addWidget(self._pause_btn)
|
|
|
+ buttons_layout.addWidget(self._cancel_btn)
|
|
|
+ controls_layout.addLayout(buttons_layout)
|
|
|
+
|
|
|
+ # Progress section
|
|
|
+ progress_group = QWidget()
|
|
|
+ progress_layout = QVBoxLayout(progress_group)
|
|
|
+ progress_layout.setSpacing(4)
|
|
|
+
|
|
|
+ progress_label = QLabel("Translation Progress")
|
|
|
+ progress_label.setFont(QFont("", 11, QFont.Weight.Bold))
|
|
|
+ progress_layout.addWidget(progress_label)
|
|
|
+
|
|
|
+ # Task status
|
|
|
+ self._task_status_label = QLabel("No active task")
|
|
|
+ self._task_status_label.setStyleSheet("color: gray;")
|
|
|
+ progress_layout.addWidget(self._task_status_label)
|
|
|
+
|
|
|
+ # Progress bar (will be added in Story 7.9)
|
|
|
+ from PyQt6.QtWidgets import QProgressBar
|
|
|
+ self._progress_bar = QProgressBar()
|
|
|
+ self._progress_bar.setRange(0, 100)
|
|
|
+ self._progress_bar.setValue(0)
|
|
|
+ self._progress_bar.setTextVisible(True)
|
|
|
+ progress_layout.addWidget(self._progress_bar)
|
|
|
+
|
|
|
+ # Statistics
|
|
|
+ stats_layout = QHBoxLayout()
|
|
|
+ self._files_label = QLabel("Files: 0")
|
|
|
+ self._chapters_label = QLabel("Chapters: 0")
|
|
|
+ self._words_label = QLabel("Words: 0")
|
|
|
+ self._time_label = QLabel("ETA: --:--")
|
|
|
+
|
|
|
+ stats_layout.addWidget(self._files_label)
|
|
|
+ stats_layout.addWidget(self._chapters_label)
|
|
|
+ stats_layout.addWidget(self._words_label)
|
|
|
+ stats_layout.addStretch()
|
|
|
+ stats_layout.addWidget(self._time_label)
|
|
|
+ progress_layout.addLayout(stats_layout)
|
|
|
+
|
|
|
+ layout.addWidget(controls_group)
|
|
|
+ layout.addWidget(progress_group)
|
|
|
+ layout.addStretch()
|
|
|
+
|
|
|
+ return panel
|
|
|
+
|
|
|
+ def _create_menu_bar(self) -> None:
|
|
|
+ """Create the application menu bar."""
|
|
|
+ menubar = self.menuBar()
|
|
|
+
|
|
|
+ # File Menu
|
|
|
+ file_menu = menubar.addMenu("&File")
|
|
|
+
|
|
|
+ add_files_action = QAction("Add &Files...", self)
|
|
|
+ add_files_action.setShortcut(QKeySequence.StandardKey.Open)
|
|
|
+ add_files_action.setStatusTip("Add files to translation queue")
|
|
|
+ add_files_action.triggered.connect(self._on_add_files)
|
|
|
+ file_menu.addAction(add_files_action)
|
|
|
+
|
|
|
+ add_folder_action = QAction("Add &Folder...", self)
|
|
|
+ add_folder_action.setStatusTip("Add all files from a folder")
|
|
|
+ add_folder_action.triggered.connect(self._on_add_folder)
|
|
|
+ file_menu.addAction(add_folder_action)
|
|
|
+
|
|
|
+ file_menu.addSeparator()
|
|
|
+
|
|
|
+ export_action = QAction("E&xport Results...", self)
|
|
|
+ export_action.setShortcut(QKeySequence.StandardKey.Save)
|
|
|
+ export_action.setStatusTip("Export translation results")
|
|
|
+ export_action.setEnabled(False) # TODO: Implement in Story 7.17
|
|
|
+ file_menu.addAction(export_action)
|
|
|
+
|
|
|
+ file_menu.addSeparator()
|
|
|
+
|
|
|
+ exit_action = QAction("E&xit", self)
|
|
|
+ exit_action.setShortcut(QKeySequence.StandardKey.Quit)
|
|
|
+ exit_action.setStatusTip("Exit application")
|
|
|
+ exit_action.triggered.connect(self.close)
|
|
|
+ file_menu.addAction(exit_action)
|
|
|
+
|
|
|
+ # Edit Menu
|
|
|
+ edit_menu = menubar.addMenu("&Edit")
|
|
|
+
|
|
|
+ select_all_action = QAction("Select &All", self)
|
|
|
+ select_all_action.setShortcut(QKeySequence.StandardKey.SelectAll)
|
|
|
+ select_all_action.setStatusTip("Select all files")
|
|
|
+ select_all_action.triggered.connect(self._on_select_all)
|
|
|
+ edit_menu.addAction(select_all_action)
|
|
|
+
|
|
|
+ edit_menu.addSeparator()
|
|
|
+
|
|
|
+ settings_action = QAction("&Settings...", self)
|
|
|
+ settings_action.setShortcut(QKeySequence.StandardKey.Preferences)
|
|
|
+ settings_action.setStatusTip("Open settings dialog")
|
|
|
+ settings_action.triggered.connect(self._on_settings)
|
|
|
+ edit_menu.addAction(settings_action)
|
|
|
+
|
|
|
+ # View Menu
|
|
|
+ view_menu = menubar.addMenu("&View")
|
|
|
+
|
|
|
+ log_viewer_action = QAction("&Log Viewer...", self)
|
|
|
+ log_viewer_action.setStatusTip("View application logs")
|
|
|
+ log_viewer_action.setEnabled(False) # TODO: Implement in Story 7.15
|
|
|
+ view_menu.addAction(log_viewer_action)
|
|
|
+
|
|
|
+ statistics_action = QAction("&Statistics...", self)
|
|
|
+ statistics_action.setStatusTip("View translation statistics")
|
|
|
+ statistics_action.setEnabled(False) # TODO: Implement in Story 7.23
|
|
|
+ view_menu.addAction(statistics_action)
|
|
|
+
|
|
|
+ # Tools Menu
|
|
|
+ tools_menu = menubar.addMenu("&Tools")
|
|
|
+
|
|
|
+ terminology_action = QAction("&Terminology Editor...", self)
|
|
|
+ terminology_action.setStatusTip("Manage terminology")
|
|
|
+ terminology_action.setEnabled(False) # TODO: Implement in Story 7.19
|
|
|
+ tools_menu.addAction(terminology_action)
|
|
|
+
|
|
|
+ # Help Menu
|
|
|
+ help_menu = menubar.addMenu("&Help")
|
|
|
+
|
|
|
+ documentation_action = QAction("&Documentation", self)
|
|
|
+ documentation_action.setShortcut(QKeySequence.StandardKey.HelpContents)
|
|
|
+ documentation_action.setStatusTip("View documentation")
|
|
|
+ documentation_action.setEnabled(False)
|
|
|
+ help_menu.addAction(documentation_action)
|
|
|
+
|
|
|
+ help_menu.addSeparator()
|
|
|
+
|
|
|
+ about_action = QAction("&About", self)
|
|
|
+ about_action.setStatusTip("About this application")
|
|
|
+ about_action.triggered.connect(self._on_about)
|
|
|
+ help_menu.addAction(about_action)
|
|
|
+
|
|
|
+ def _create_toolbar(self) -> None:
|
|
|
+ """Create the main toolbar."""
|
|
|
+ toolbar = QToolBar("Main Toolbar")
|
|
|
+ toolbar.setMovable(False)
|
|
|
+ self.addToolBar(toolbar)
|
|
|
+
|
|
|
+ # Add Files action
|
|
|
+ add_files_action = QAction("Add Files", self)
|
|
|
+ add_files_action.setStatusTip("Add files to translation queue")
|
|
|
+ add_files_action.triggered.connect(self._on_add_files)
|
|
|
+ toolbar.addAction(add_files_action)
|
|
|
+
|
|
|
+ # Add Folder action
|
|
|
+ add_folder_action = QAction("Add Folder", self)
|
|
|
+ add_folder_action.setStatusTip("Add folder to translation queue")
|
|
|
+ add_folder_action.triggered.connect(self._on_add_folder)
|
|
|
+ toolbar.addAction(add_folder_action)
|
|
|
+
|
|
|
+ toolbar.addSeparator()
|
|
|
+
|
|
|
+ # Start action
|
|
|
+ self._start_action = QAction("Start", self)
|
|
|
+ self._start_action.setStatusTip("Start translation")
|
|
|
+ self._start_action.setEnabled(False)
|
|
|
+ self._start_action.triggered.connect(self._on_start_translation)
|
|
|
+ toolbar.addAction(self._start_action)
|
|
|
+
|
|
|
+ # Pause action
|
|
|
+ self._pause_action = QAction("Pause", self)
|
|
|
+ self._pause_action.setStatusTip("Pause translation")
|
|
|
+ self._pause_action.setEnabled(False)
|
|
|
+ self._pause_action.triggered.connect(self._on_pause_translation)
|
|
|
+ toolbar.addAction(self._pause_action)
|
|
|
+
|
|
|
+ # Cancel action
|
|
|
+ self._cancel_action = QAction("Cancel", self)
|
|
|
+ self._cancel_action.setStatusTip("Cancel translation")
|
|
|
+ self._cancel_action.setEnabled(False)
|
|
|
+ self._cancel_action.triggered.connect(self._on_cancel_translation)
|
|
|
+ toolbar.addAction(self._cancel_action)
|
|
|
+
|
|
|
+ toolbar.addSeparator()
|
|
|
+
|
|
|
+ # Settings action
|
|
|
+ settings_action = QAction("Settings", self)
|
|
|
+ settings_action.setStatusTip("Open settings")
|
|
|
+ settings_action.triggered.connect(self._on_settings)
|
|
|
+ toolbar.addAction(settings_action)
|
|
|
+
|
|
|
+ def _create_status_bar(self) -> None:
|
|
|
+ """Create the status bar."""
|
|
|
+ status_bar = QStatusBar()
|
|
|
+ self.setStatusBar(status_bar)
|
|
|
+
|
|
|
+ # Status label
|
|
|
+ self._status_label = QLabel("Ready")
|
|
|
+ status_bar.addWidget(self._status_label)
|
|
|
+
|
|
|
+ # Permanent widgets (right side)
|
|
|
+ status_bar.addPermanentWidget(QLabel(f"v{self.APPLICATION_VERSION}"))
|
|
|
+
|
|
|
+ def _connect_signals(self) -> None:
|
|
|
+ """Connect internal signals."""
|
|
|
+ # Button signals
|
|
|
+ self._add_file_btn.clicked.connect(self._on_add_files)
|
|
|
+ self._add_folder_btn.clicked.connect(self._on_add_folder)
|
|
|
+ self._remove_file_btn.clicked.connect(self._on_remove_files)
|
|
|
+ self._clear_all_btn.clicked.connect(self._on_clear_all)
|
|
|
+ self._start_btn.clicked.connect(self._on_start_translation)
|
|
|
+ self._pause_btn.clicked.connect(self._on_pause_translation)
|
|
|
+ self._cancel_btn.clicked.connect(self._on_cancel_translation)
|
|
|
+
|
|
|
+ # Selection change
|
|
|
+ self._file_table.selectionModel().selectionChanged.connect(
|
|
|
+ self._on_selection_changed
|
|
|
+ )
|
|
|
+
|
|
|
+ def _on_add_files(self) -> None:
|
|
|
+ """Handle add files button click."""
|
|
|
+ # TODO: Implement file selection dialog in Story 7.8
|
|
|
+ self._status_label.setText("Add files - TODO: Implement file dialog")
|
|
|
+
|
|
|
+ def _on_add_folder(self) -> None:
|
|
|
+ """Handle add folder button click."""
|
|
|
+ # TODO: Implement folder selection dialog in Story 7.8
|
|
|
+ self._status_label.setText("Add folder - TODO: Implement folder dialog")
|
|
|
+
|
|
|
+ def _on_remove_files(self) -> None:
|
|
|
+ """Handle remove files button click."""
|
|
|
+ # TODO: Implement file removal logic
|
|
|
+ self._status_label.setText("Remove files - TODO")
|
|
|
+
|
|
|
+ def _on_clear_all(self) -> None:
|
|
|
+ """Handle clear all button click."""
|
|
|
+ # TODO: Implement clear all logic
|
|
|
+ self._status_label.setText("Clear all - TODO")
|
|
|
+
|
|
|
+ def _on_select_all(self) -> None:
|
|
|
+ """Handle select all menu action."""
|
|
|
+ self._file_table.selectAll()
|
|
|
+
|
|
|
+ def _on_settings(self) -> None:
|
|
|
+ """Handle settings menu action."""
|
|
|
+ self.settings_requested.emit()
|
|
|
+
|
|
|
+ def _on_about(self) -> None:
|
|
|
+ """Handle about menu action."""
|
|
|
+ self.about_requested.emit()
|
|
|
+
|
|
|
+ def _on_start_translation(self) -> None:
|
|
|
+ """Handle start translation button click."""
|
|
|
+ self.translation_started.emit()
|
|
|
+
|
|
|
+ def _on_pause_translation(self) -> None:
|
|
|
+ """Handle pause translation button click."""
|
|
|
+ self.translation_paused.emit()
|
|
|
+
|
|
|
+ def _on_cancel_translation(self) -> None:
|
|
|
+ """Handle cancel translation button click."""
|
|
|
+ self.translation_cancelled.emit()
|
|
|
+
|
|
|
+ def _on_selection_changed(self) -> None:
|
|
|
+ """Handle file table selection change."""
|
|
|
+ has_selection = len(self._file_table.selectionModel().selectedRows()) > 0
|
|
|
+ self._remove_file_btn.setEnabled(has_selection)
|
|
|
+
|
|
|
+ def add_file_item(self, item: FileItem) -> None:
|
|
|
+ """Add a file item to the list."""
|
|
|
+ self._file_items.append(item)
|
|
|
+ self._file_model.layoutChanged.emit()
|
|
|
+ self._update_file_count()
|
|
|
+ self._update_control_buttons()
|
|
|
+ self.file_added.emit(item)
|
|
|
+
|
|
|
+ def remove_file_item(self, item: FileItem) -> None:
|
|
|
+ """Remove a file item from the list."""
|
|
|
+ if item in self._file_items:
|
|
|
+ self._file_items.remove(item)
|
|
|
+ self._file_model.layoutChanged.emit()
|
|
|
+ self._update_file_count()
|
|
|
+ self._update_control_buttons()
|
|
|
+ self.file_removed.emit(item)
|
|
|
+
|
|
|
+ def clear_all_files(self) -> None:
|
|
|
+ """Clear all file items."""
|
|
|
+ self._file_items.clear()
|
|
|
+ self._file_model.layoutChanged.emit()
|
|
|
+ self._update_file_count()
|
|
|
+ self._update_control_buttons()
|
|
|
+
|
|
|
+ def _update_file_count(self) -> None:
|
|
|
+ """Update file count display."""
|
|
|
+ count = len(self._file_items)
|
|
|
+ self._files_label.setText(f"Files: {count}")
|
|
|
+
|
|
|
+ def _update_control_buttons(self) -> None:
|
|
|
+ """Update control button states."""
|
|
|
+ has_files = len(self._file_items) > 0
|
|
|
+ self._start_btn.setEnabled(has_files)
|
|
|
+ self._start_action.setEnabled(has_files)
|
|
|
+ self._clear_all_btn.setEnabled(has_files)
|
|
|
+
|
|
|
+ def set_task_status(self, status: str) -> None:
|
|
|
+ """Update task status display."""
|
|
|
+ self._task_status_label.setText(status)
|
|
|
+
|
|
|
+ def set_progress(self, value: int) -> None:
|
|
|
+ """Update progress bar value."""
|
|
|
+ self._progress_bar.setValue(value)
|
|
|
+
|
|
|
+ def set_status_message(self, message: str) -> None:
|
|
|
+ """Update status bar message."""
|
|
|
+ self._status_label.setText(message)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def file_items(self) -> list[FileItem]:
|
|
|
+ """Get list of file items."""
|
|
|
+ return self._file_items.copy()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def selected_file_items(self) -> list[FileItem]:
|
|
|
+ """Get currently selected file items."""
|
|
|
+ selected = []
|
|
|
+ for index in self._file_table.selectionModel().selectedRows():
|
|
|
+ row = index.row()
|
|
|
+ if 0 <= row < len(self._file_items):
|
|
|
+ selected.append(self._file_items[row])
|
|
|
+ return selected
|