diff --git a/portprotonqt/delete_wine_module.py b/portprotonqt/delete_wine_module.py new file mode 100644 index 0000000..2bb0141 --- /dev/null +++ b/portprotonqt/delete_wine_module.py @@ -0,0 +1,222 @@ +import os +import shutil +from PySide6.QtWidgets import (QDialog, QVBoxLayout, QWidget, QCheckBox, + QPushButton, QMessageBox, + QLabel, QTextEdit, QHBoxLayout, + QListWidget, QListWidgetItem) +from PySide6.QtCore import Qt +from portprotonqt.localization import _ + + +class WineDeleteManager(QDialog): + def __init__(self, parent=None, portproton_location=None, selected_wine=None): + super().__init__(parent) + self.selected_wines = set() # Use set to store selected wine names + self.portproton_location = portproton_location + self.selected_wine = selected_wine # The wine that should be pre-selected + self.initUI() + self.load_wine_data() + + def initUI(self): + self.setWindowTitle(_('Delete Wine')) + self.resize(800, 600) + + layout = QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # Wine list widget - основной растягивающийся элемент + self.list_widget = QListWidget() + self.list_widget.setSelectionMode(QListWidget.SelectionMode.NoSelection) # Disable default selection to use checkboxes + layout.addWidget(self.list_widget, 1) + + # Инфо-блок для показа выбранного (компактный для информации по выбранным закачкам) + selection_widget = QWidget() + selection_layout = QVBoxLayout(selection_widget) + selection_layout.setContentsMargins(0, 2, 0, 2) + selection_layout.setSpacing(2) + + selection_label = QLabel(_("Selected WINE:")) + selection_layout.addWidget(selection_label) + + self.selection_text = QTextEdit() + self.selection_text.setMaximumHeight(80) + self.selection_text.setReadOnly(True) + self.selection_text.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction) + self.selection_text.setPlainText(_("No WINE selected")) + selection_layout.addWidget(self.selection_text) + + layout.addWidget(selection_widget) + + # Кнопки управления + button_layout = QHBoxLayout() + self.delete_btn = QPushButton(_('Delete Selected')) + self.delete_btn.clicked.connect(self.delete_selected) + self.delete_btn.setEnabled(False) + self.delete_btn.setMinimumHeight(40) + self.clear_btn = QPushButton(_('Clear All')) + self.clear_btn.clicked.connect(self.clear_selection) + self.clear_btn.setMinimumHeight(40) + button_layout.addWidget(self.delete_btn) + button_layout.addWidget(self.clear_btn) + layout.addLayout(button_layout) + + def load_wine_data(self): + """Load wine data from dist directory""" + if not self.portproton_location: + return + + dist_path = os.path.join(self.portproton_location, "data", "dist") + if not os.path.exists(dist_path): + return + + # Get all wine directories + wine_dirs = [d for d in os.listdir(dist_path) if os.path.isdir(os.path.join(dist_path, d))] + + # Add each wine to the list + for wine_name in wine_dirs: + self.add_wine_to_list(wine_name) + + def add_wine_to_list(self, wine_name): + """Add a wine to the list with checkbox""" + # Create a widget with checkbox and wine name + item_widget = QWidget() + item_layout = QHBoxLayout(item_widget) + item_layout.setContentsMargins(5, 2, 5, 2) + item_layout.setSpacing(5) + + checkbox = QCheckBox(wine_name) + + + checkbox.stateChanged.connect(lambda state, name=wine_name: self.on_wine_toggled(state, name)) + item_layout.addWidget(checkbox) + item_layout.addStretch() # Add stretch to align checkbox to the left + + # Create list item and set the widget + list_item = QListWidgetItem(self.list_widget) + list_item.setSizeHint(item_widget.sizeHint()) + self.list_widget.addItem(list_item) + self.list_widget.setItemWidget(list_item, item_widget) + + + def on_wine_toggled(self, state, wine_name): + """Handle wine selection/deselection""" + if state == Qt.CheckState.Checked.value: + self.selected_wines.add(wine_name) + else: + self.selected_wines.discard(wine_name) + + self.update_selection_display() + + def update_selection_display(self): + """Update the selection display""" + # Get currently selected wines from the list + currently_selected = set() + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + item_widget = self.list_widget.itemWidget(item) + if item_widget: + checkbox = item_widget.findChild(QCheckBox) + if checkbox and checkbox.isChecked(): + currently_selected.add(checkbox.text()) + + # Update the internal set to match the current state + self.selected_wines = currently_selected + + if self.selected_wines: + selection_text = _('Selected {} WINE:\n').format(len(self.selected_wines)) + + for i, wine_name in enumerate(sorted(self.selected_wines), 1): + selection_text += f"{i}. {wine_name}\n" + + self.selection_text.setPlainText(selection_text) + self.delete_btn.setEnabled(True) + else: + self.selection_text.setPlainText(_("No WINE selected")) + self.delete_btn.setEnabled(False) + + def clear_selection(self): + """Clear all selections""" + self.selected_wines.clear() + + # Uncheck all checkboxes in the list + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + item_widget = self.list_widget.itemWidget(item) + if item_widget: + checkbox = item_widget.findChild(QCheckBox) + if checkbox: + checkbox.setChecked(False) + + self.update_selection_display() + + def delete_selected(self): + """Delete all selected wines""" + # Get currently selected wines from the list + currently_selected = set() + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + item_widget = self.list_widget.itemWidget(item) + if item_widget: + checkbox = item_widget.findChild(QCheckBox) + if checkbox and checkbox.isChecked(): + currently_selected.add(checkbox.text()) + + if not currently_selected: + QMessageBox.warning(self, _("No Selection"), _("Please select at least one WINE to delete.")) + return + + # Confirm deletion + wine_list = "\n".join(sorted(currently_selected)) + reply = QMessageBox.question( + self, + _("Confirm Deletion"), + _("Are you sure you want to delete the following WINE versions?\n\n{}").format(wine_list), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No + ) + + if reply != QMessageBox.StandardButton.Yes: + return + + if not self.portproton_location: + return + + dist_path = os.path.join(self.portproton_location, "data", "dist") + errors = [] + + for wine_name in currently_selected: + wine_path = os.path.join(dist_path, wine_name) + try: + if os.path.exists(wine_path): + shutil.rmtree(wine_path) + except Exception as e: + error_msg = _("Failed to delete WINE '{}': {}").format(wine_name, str(e)) + errors.append(error_msg) + QMessageBox.warning(self, _("Error"), error_msg) + + if errors: + QMessageBox.warning(self, _("Some Deletions Failed"), + _("Some WINE versions could not be deleted:\n\n{}").format("\n".join(errors))) + else: + QMessageBox.information(self, _("Success"), _("Selected WINE versions deleted successfully.")) + + # Close the dialog after deletion + self.accept() + + +def show_wine_delete_manager(parent=None, portproton_location=None, selected_wine=None): + """ + Shows the WINE deletion dialog. + + Args: + parent: Parent widget for the dialog + portproton_location: Location of PortProton installation + selected_wine: Wine that should be pre-selected + + Returns: + WineDeleteManager dialog instance + """ + dialog = WineDeleteManager(parent, portproton_location, selected_wine) + dialog.exec() # Use exec() for modal dialog + return dialog diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py index b40b03f..2c48f6b 100644 --- a/portprotonqt/main_window.py +++ b/portprotonqt/main_window.py @@ -1984,37 +1984,26 @@ class MainWindow(QMainWindow): QMessageBox.warning(self, _("Error"), _("Failed to delete prefix: {}").format(str(e))) def delete_compat_tool(self): - """Удаляет выбранный Wine/Proton дистрибутив из каталога dist.""" + """Shows the WINE deletion dialog to select and delete wine versions.""" + from portprotonqt.delete_wine_module import show_wine_delete_manager + selected_tool = self.wineCombo.currentText() + show_wine_delete_manager(self, self.portproton_location, selected_tool) + # Refresh the combo box after deletion + self.refresh_wine_combo() + + def refresh_wine_combo(self): + """Refresh the wine combo box after deletion.""" if not self.portproton_location: return - selected_tool = self.wineCombo.currentText() - if not selected_tool: + dist_path = os.path.join(self.portproton_location, "data", "dist") + if not os.path.exists(dist_path): return - tool_path = os.path.join(self.portproton_location, "data", "dist", selected_tool) - if not os.path.exists(tool_path): - return - - reply = QMessageBox.question( - self, - _("Confirm Deletion"), - _("Are you sure you want to delete compatibility tool '{}'?").format(selected_tool), - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - try: - shutil.rmtree(tool_path) - QMessageBox.information(self, _("Success"), _("Compatibility tool '{}' deleted.").format(selected_tool)) - # обновляем список - self.wineCombo.clear() - self.wine_versions = [d for d in os.listdir(os.path.join(self.portproton_location, "data", "dist")) - if os.path.isdir(os.path.join(self.portproton_location, "data", "dist", d))] - self.wineCombo.addItems(self.wine_versions) - except Exception as e: - QMessageBox.warning(self, _("Error"), _("Failed to delete compatibility tool: {}").format(str(e))) + # Update the wine versions list + self.wine_versions = [d for d in os.listdir(dist_path) if os.path.isdir(os.path.join(dist_path, d))] + self.wineCombo.clear() + self.wineCombo.addItems(self.wine_versions) def open_winetricks(self): """Open the Winetricks dialog for the selected prefix and wine."""