feat(get_wine): added Installed tab for wine remove

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2026-01-11 20:02:50 +05:00
parent f08becdbde
commit 96766643b7
2 changed files with 336 additions and 23 deletions

View File

@@ -138,7 +138,7 @@ def create_dialog_hints_widget(theme, main_window, input_manager, context='defau
elif context == 'proton_manager':
dialog_actions = [
("confirm", _("Toggle")), # A / Cross
("add_game", _("Download")), # X / Triangle
("add_game", _("Apply")), # X / Triangle
("prev_dir", _("Clear All")), # Y / Square
("back", _("Cancel")), # B / Circle
("prev_tab", _("Prev Tab")), # LB / L1

View File

@@ -355,13 +355,14 @@ class ProtonManager(QDialog):
self.initUI()
self.load_proton_data_from_json()
self.create_installed_tab()
# Enable gamepad support if input manager is provided
if self.input_manager:
self.enable_proton_manager_mode()
def initUI(self):
self.setWindowTitle(_('Get other Wine'))
self.setWindowTitle(_('Manage Wine versions'))
self.resize(1100, 720)
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
@@ -441,6 +442,9 @@ class ProtonManager(QDialog):
button_layout.addWidget(self.clear_btn)
layout.addLayout(button_layout)
# Connect tab change signal
self.tab_widget.currentChanged.connect(self.tab_changed)
# Connect signals for hints updates
if self.input_manager and self.main_window:
self.input_manager.button_event.connect(
@@ -764,6 +768,132 @@ class ProtonManager(QDialog):
return os.path.exists(expected_dir)
def create_installed_tab(self):
"""Create the 'Installed' tab showing installed wine versions with removal option"""
if not self.portproton_location:
return
dist_path = os.path.join(self.portproton_location, "data", "dist")
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
installed_versions = [d for d in os.listdir(dist_path) if os.path.isdir(os.path.join(dist_path, d))]
if not installed_versions:
# Create empty tab with message
tab = QWidget()
layout = QVBoxLayout(tab)
label = QLabel(_("No Wine/Proton versions installed"))
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
label.setStyleSheet("font-size: 16px; padding: 50px;")
layout.addWidget(label)
self.tab_widget.addTab(tab, _("Installed"))
return
# Create tab with table for installed versions
tab = QWidget()
tab.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
layout = QVBoxLayout(tab)
layout.setContentsMargins(5, 5, 5, 5)
layout.setSpacing(5)
table = QTableWidget()
table.setAlternatingRowColors(True)
table.verticalHeader().setVisible(False)
table.setColumnCount(3) # Checkbox, Name, Size
table.setHorizontalHeaderLabels(['', _('Version Name'), _('Size')])
table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
table.verticalHeader().setDefaultSectionSize(36)
table.setStyleSheet(self.theme.GETWINE_WINDOW_STYLE)
header = table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
# Sort installed versions
installed_versions.sort(key=version_sort_key)
table.setRowCount(len(installed_versions))
table.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
for row_index, version_name in enumerate(installed_versions):
self.add_installed_row(table, row_index, version_name)
layout.addWidget(table, 1)
# Настройка выделения строк и обработчика кликов для вкладки Installed
table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection)
table.cellClicked.connect(self.on_cell_clicked)
self.tab_widget.addTab(tab, _("Installed"))
def add_installed_row(self, table, row_index, version_name):
"""Add a row for an installed version with delete option"""
checkbox_widget = QWidget()
checkbox_layout = QHBoxLayout(checkbox_widget)
checkbox_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
checkbox_layout.setContentsMargins(0, 0, 0, 0)
checkbox = QCheckBox()
checkbox_widget.setToolTip(_("Select to remove this version"))
checkbox.stateChanged.connect(lambda state: self.on_installed_version_toggled(state))
checkbox_layout.addWidget(checkbox)
table.setCellWidget(row_index, 0, checkbox_widget)
# Add version name
version_item = QTableWidgetItem(version_name)
version_item.setFlags(version_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
table.setItem(row_index, 1, version_item)
# Calculate and add size
if self.portproton_location:
dist_path = os.path.join(self.portproton_location, "data", "dist")
version_path = os.path.join(dist_path, version_name)
size_str = self.get_directory_size(version_path)
else:
size_str = _("Unknown")
version_path = "" # Provide a default value when portproton_location is None
size_item = QTableWidgetItem(size_str)
size_item.setFlags(size_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
table.setItem(row_index, 2, size_item)
# Store version name in user data for later use
for col in range(table.columnCount()):
item = table.item(row_index, col)
if item:
item.setData(Qt.ItemDataRole.UserRole, {
'version_name': version_name,
'version_path': version_path
})
def on_installed_version_toggled(self, state):
"""Handle checkbox state changes in the installed tab"""
self.update_selection_display()
def get_directory_size(self, path):
"""Calculate directory size and return human-readable string"""
try:
total_size = 0
for dirpath, _dirnames, filenames in os.walk(path):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if os.path.exists(filepath):
total_size += os.path.getsize(filepath)
# Convert to human readable format
for unit in ['B', 'KB', 'MB', 'GB']:
if total_size < 1024.0:
return f"{total_size:.1f} {unit}"
total_size /= 1024.0
return f"{total_size:.1f} TB"
except Exception:
return _("Unknown")
def on_cell_clicked(self, row):
"""Обработка клика по ячейке - переключение флажка при клике по любой ячейке в строке"""
tab = self.tab_widget.currentWidget()
@@ -774,6 +904,8 @@ class ProtonManager(QDialog):
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isEnabled():
checkbox.setChecked(not checkbox.isChecked())
# Update selection display after clicking
self.update_selection_display()
def on_asset_toggled_json(self, state, asset, version, source_name):
"""Обработка выбора/отмены выбора элемента из данных JSON"""
@@ -806,17 +938,102 @@ class ProtonManager(QDialog):
def update_selection_display(self):
"""Обновляем отображение выбора"""
if self.selected_assets:
selection_text = _('Selected {} assets:\n').format(len(self.selected_assets))
current_tab_index = self.tab_widget.currentIndex()
current_tab_text = self.tab_widget.tabText(current_tab_index)
for i, asset_data in enumerate(self.selected_assets.values(), 1):
selection_text += f"{i}. {asset_data['source_name'].upper()} - {asset_data['asset_name']}\n"
if current_tab_text == _("Installed"):
# Handle installed tab - count selected checkboxes
current_tab = self.tab_widget.currentWidget()
table = current_tab.findChild(QTableWidget)
if table:
selected_count = 0
for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0)
if checkbox_widget:
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isChecked():
selected_count += 1
self.selection_text.setPlainText(selection_text)
self.download_btn.setEnabled(True)
if selected_count > 0:
selection_text = _('Selected {} assets:\n').format(selected_count)
# Add the specific version names that are selected
current_tab = self.tab_widget.currentWidget()
table = current_tab.findChild(QTableWidget)
if table:
# Create a counter for numbering the selected items
item_number = 1
for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0)
if checkbox_widget:
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isChecked():
version_item = table.item(row, 1) # Version name column
if version_item:
version_name = version_item.text()
selection_text += f"{item_number}. {version_name}\n"
item_number += 1
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(True)
else:
selection_text = _("No assets selected")
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(False)
self.selection_text.setPlainText(selection_text)
else:
self.selection_text.setPlainText(_("No assets selected"))
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(False)
else:
self.selection_text.setPlainText(_("No assets selected"))
self.download_btn.setEnabled(False)
# Handle other tabs - use selected_assets dictionary
if self.selected_assets:
selection_text = _('Selected {} assets:\n').format(len(self.selected_assets))
for i, asset_data in enumerate(self.selected_assets.values(), 1):
selection_text += f"{i}. {asset_data['source_name'].upper()} - {asset_data['asset_name']}\n"
self.selection_text.setPlainText(selection_text)
self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(True)
else:
self.selection_text.setPlainText(_("No assets selected"))
self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(False)
def tab_changed(self, index):
"""Handle tab change to update button text appropriately"""
current_tab_text = self.tab_widget.tabText(index)
if current_tab_text == _("Installed"):
# Count selected items in installed tab
current_tab = self.tab_widget.widget(index)
table = current_tab.findChild(QTableWidget)
if table:
selected_count = 0
for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0)
if checkbox_widget:
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isChecked():
selected_count += 1
if selected_count > 0:
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(True)
else:
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(False)
else:
# For other tabs, use the selected_assets dictionary
if self.selected_assets:
self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(True)
else:
self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(False)
self.update_selection_display()
def clear_selection(self):
"""Очищаем (сбрасываем) всё выбранное"""
@@ -824,8 +1041,10 @@ class ProtonManager(QDialog):
QMessageBox.warning(self, _("Downloading in Progress"), _("Cannot clear selection while extraction is in progress."))
return
# Clear selected assets for download tabs
self.selected_assets.clear()
# Clear checkboxes in all tabs
for tab_index in range(self.tab_widget.count()):
tab = self.tab_widget.widget(tab_index)
table = tab.findChild(QTableWidget)
@@ -834,29 +1053,123 @@ class ProtonManager(QDialog):
checkbox_widget = table.cellWidget(row, 0)
if checkbox_widget:
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isEnabled():
if checkbox:
checkbox.setChecked(False)
self.update_selection_display()
def download_selected(self):
"""Extract all selected archives"""
if not self.selected_assets:
QMessageBox.warning(self, _("No Selection"), _("Please select at least one archive to download."))
"""Handle both downloading new versions and removing installed versions"""
# Check if we're on the Installed tab
current_tab_index = self.tab_widget.currentIndex()
current_tab_text = self.tab_widget.tabText(current_tab_index)
if current_tab_text == _("Installed"):
# Handle removal of selected installed versions
self.remove_selected_installed_versions()
else:
# Handle downloading of selected versions (existing functionality)
if not self.selected_assets:
QMessageBox.warning(self, _("No Selection"), _("Please select at least one archive to download."))
return
if self.is_downloading:
QMessageBox.warning(self, _("Downloading in Progress"), _("Please wait for current downloading to complete."))
return
downloads_dir = "proton_downloads"
if not os.path.exists(downloads_dir):
os.makedirs(downloads_dir)
self.assets_to_download = list(self.selected_assets.values())
self.current_download_index = 0
self.is_downloading = True
self.start_next_download()
def remove_selected_installed_versions(self):
"""Delete selected installed wine/proton versions"""
# Get the current tab (Installed tab)
current_tab = self.tab_widget.currentWidget()
table = current_tab.findChild(QTableWidget)
if not table:
return
if self.is_downloading:
QMessageBox.warning(self, _("Downloading in Progress"), _("Please wait for current downloading to complete."))
# Find all selected versions to remove
versions_to_remove = []
for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0)
if checkbox_widget:
checkbox = checkbox_widget.findChild(QCheckBox)
if checkbox and checkbox.isChecked():
item = table.item(row, 1) # Version name column
if item:
user_data = item.data(Qt.ItemDataRole.UserRole)
if user_data:
versions_to_remove.append(user_data['version_path'])
if not versions_to_remove:
# Temporarily disable proton manager mode to allow gamepad input in QMessageBox
if self.input_manager:
self.disable_proton_manager_mode()
try:
QMessageBox.warning(self, _("No Selection"), _("Please select at least one version to delete."))
finally:
# Re-enable proton manager mode after QMessageBox closes
if self.input_manager:
self.enable_proton_manager_mode()
return
downloads_dir = "proton_downloads"
if not os.path.exists(downloads_dir):
os.makedirs(downloads_dir)
# Temporarily disable proton manager mode to allow gamepad input in QMessageBox
if self.input_manager:
self.disable_proton_manager_mode()
try:
# Confirm deletion
reply = QMessageBox.question(
self,
_("Confirm Deletion"),
_("Are you sure you want to delete {} selected version(s)?\n\nThis action cannot be undone.").format(len(versions_to_remove)),
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
finally:
# Re-enable proton manager mode after QMessageBox closes
if self.input_manager:
self.enable_proton_manager_mode()
self.assets_to_download = list(self.selected_assets.values())
self.current_download_index = 0
self.is_downloading = True
self.start_next_download()
if reply != QMessageBox.StandardButton.Yes:
return
# Remove the selected versions
removed_count = 0
for version_path in versions_to_remove:
try:
if os.path.exists(version_path):
import shutil
shutil.rmtree(version_path)
removed_count += 1
except Exception as e:
logger.error(f"Error removing version at {version_path}: {e}")
QMessageBox.warning(self, _("Error"), _("Failed to remove version at {}: {}").format(version_path, str(e)))
if removed_count > 0:
QMessageBox.information(self, _("Success"), _("Successfully removed {} version(s).").format(removed_count))
# Refresh the installed tab to show updated list
self.refresh_installed_tab()
def refresh_installed_tab(self):
"""Refresh the installed tab to show current installed versions"""
# Find the installed tab index
installed_tab_index = -1
for i in range(self.tab_widget.count()):
if self.tab_widget.tabText(i) == _("Installed"):
installed_tab_index = i
break
if installed_tab_index != -1:
# Remove the old installed tab
self.tab_widget.removeTab(installed_tab_index)
# Create a new one
self.create_installed_tab()
def start_next_download(self):
"""Start extraction of next archive in the list"""