| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014 |
- # -*- coding: utf-8 -*-
- #
- # Copyright © 2012 Pierre Raybaut
- # Licensed under the terms of the MIT License
- # (see winpython/__init__.py for details)
- """
- WinPython Package Manager GUI
- Created on Mon Aug 13 11:40:01 2012
- """
- import os.path as osp
- import os
- import sys
- import platform
- import locale
- # winpython.qt becomes winpython._vendor.qtpy
- from winpython._vendor.qtpy.QtWidgets import (
- QApplication,
- QMainWindow,
- QWidget,
- QLineEdit,
- QHBoxLayout,
- QVBoxLayout,
- QMessageBox,
- QAbstractItemView,
- QProgressDialog,
- QTableView,
- QPushButton,
- QLabel,
- QTabWidget,
- QToolTip,
- )
- from winpython._vendor.qtpy.QtGui import (
- QColor,
- QDesktopServices,
- )
- from winpython._vendor.qtpy.QtCore import (
- Qt,
- QAbstractTableModel,
- QModelIndex,
- Signal,
- QThread,
- QTimer,
- QUrl,
- )
- from winpython._vendor.qtpy.compat import (
- to_qvariant,
- getopenfilenames,
- getexistingdirectory,
- )
- import winpython._vendor.qtpy
- from winpython.qthelpers import (
- get_icon,
- add_actions,
- create_action,
- keybinding,
- get_std_icon,
- action2button,
- mimedata2url,
- )
- # Local imports
- from winpython import __version__, __project_url__
- from winpython import wppm, associate, utils
- from winpython.py3compat import getcwd, to_text_string
- COLUMNS = ACTION, CHECK, NAME, VERSION, DESCRIPTION = list(
- range(5)
- )
- class PackagesModel(QAbstractTableModel):
- # Signals after PyQt4 old SIGNAL removal
- dataChanged = Signal(QModelIndex, QModelIndex)
- def __init__(self):
- QAbstractTableModel.__init__(self)
- self.packages = []
- self.checked = set()
- self.actions = {}
- def sortByName(self):
- self.packages = sorted(
- self.packages, key=lambda x: x.name
- )
- self.reset()
- def flags(self, index):
- if not index.isValid():
- return Qt.ItemIsEnabled
- column = index.column()
- if column in (NAME, VERSION, ACTION, DESCRIPTION):
- return Qt.ItemFlags(
- QAbstractTableModel.flags(self, index)
- )
- else:
- return Qt.ItemFlags(
- QAbstractTableModel.flags(self, index)
- | Qt.ItemIsUserCheckable
- | Qt.ItemIsEditable
- )
- def data(self, index, role=Qt.DisplayRole):
- if not index.isValid() or not (
- 0 <= index.row() < len(self.packages)
- ):
- return to_qvariant()
- package = self.packages[index.row()]
- column = index.column()
- if role == Qt.CheckStateRole and column == CHECK:
- return to_qvariant(package in self.checked)
- elif role == Qt.DisplayRole:
- if column == NAME:
- return to_qvariant(package.name)
- elif column == VERSION:
- return to_qvariant(package.version)
- elif column == ACTION:
- action = self.actions.get(package)
- if action is not None:
- return to_qvariant(action)
- elif column == DESCRIPTION:
- return to_qvariant(package.description)
- elif role == Qt.TextAlignmentRole:
- if column == ACTION:
- return to_qvariant(
- int(Qt.AlignRight | Qt.AlignVCenter)
- )
- else:
- return to_qvariant(
- int(Qt.AlignLeft | Qt.AlignVCenter)
- )
- elif role == Qt.BackgroundColorRole:
- if package in self.checked:
- color = QColor(Qt.darkGreen)
- color.setAlphaF(0.1)
- return to_qvariant(color)
- else:
- color = QColor(Qt.lightGray)
- color.setAlphaF(0.3)
- return to_qvariant(color)
- return to_qvariant()
- def headerData(
- self, section, orientation, role=Qt.DisplayRole
- ):
- if role == Qt.TextAlignmentRole:
- if orientation == Qt.Horizontal:
- return to_qvariant(
- int(Qt.AlignHCenter | Qt.AlignVCenter)
- )
- return to_qvariant(
- int(Qt.AlignRight | Qt.AlignVCenter)
- )
- if role != Qt.DisplayRole:
- return to_qvariant()
- if orientation == Qt.Horizontal:
- if section == NAME:
- return to_qvariant("Name")
- elif section == VERSION:
- return to_qvariant("Version")
- elif section == ACTION:
- return to_qvariant("Action")
- elif section == DESCRIPTION:
- return to_qvariant("Description")
- return to_qvariant()
- def rowCount(self, index=QModelIndex()):
- return len(self.packages)
- def columnCount(self, index=QModelIndex()):
- return len(COLUMNS)
- def setData(self, index, value, role=Qt.EditRole):
- if (
- index.isValid()
- and 0 <= index.row() < len(self.packages)
- and role == Qt.CheckStateRole
- ):
- package = self.packages[index.row()]
- if package in self.checked:
- self.checked.remove(package)
- else:
- self.checked.add(package)
- # PyQt4 old SIGNAL: self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
- # PyQt4 old SIGNAL: index, index)
- self.dataChanged.emit(index, index)
- return True
- return False
- INSTALL_ACTION = 'Install'
- REPAIR_ACTION = 'Repair (reinstall)'
- NO_REPAIR_ACTION = 'None (Already installed)'
- UPGRADE_ACTION = 'Upgrade from v'
- NONE_ACTION = '-'
- class PackagesTable(QTableView):
- # Signals after PyQt4 old SIGNAL removal, to be emitted after package_added event
- package_added = Signal()
- def __init__(self, parent, process, winname):
- QTableView.__init__(self, parent)
- assert process in ('install', 'uninstall')
- self.process = process
- self.model = PackagesModel()
- self.setModel(self.model)
- self.winname = winname
- self.repair = False
- self.resizeColumnToContents(0)
- self.setAcceptDrops(process == 'install')
- if process == 'uninstall':
- self.hideColumn(0)
- self.distribution = None
- self.setSelectionBehavior(
- QAbstractItemView.SelectRows
- )
- self.verticalHeader().hide()
- self.setShowGrid(False)
- def reset_model(self):
- # self.model.reset() is deprecated in Qt5
- self.model.beginResetModel()
- self.model.endResetModel()
- self.horizontalHeader().setStretchLastSection(True)
- for colnb in (ACTION, CHECK, NAME, VERSION):
- self.resizeColumnToContents(colnb)
- def get_selected_packages(self):
- """Return selected packages"""
- return [
- pack
- for pack in self.model.packages
- if pack in self.model.checked
- ]
- def add_packages(self, fnames):
- """Add packages"""
- notsupported = []
- notcompatible = []
- dist = self.distribution
- for fname in fnames:
- bname = osp.basename(fname)
- try:
- package = wppm.Package(fname)
- if package.is_compatible_with(dist):
- self.add_package(package)
- else:
- notcompatible.append(bname)
- except NotImplementedError:
- notsupported.append(bname)
- # PyQt4 old SIGNAL: self.emit(SIGNAL('package_added()'))
- self.package_added.emit()
- if notsupported:
- QMessageBox.warning(
- self,
- "Warning",
- "The following packages filenaming are <b>not "
- "recognized</b> by %s:\n\n%s"
- % (self.winname, "<br>".join(notsupported)),
- QMessageBox.Ok,
- )
- if notcompatible:
- QMessageBox.warning(
- self,
- "Warning",
- "The following packages "
- "are <b>not compatible</b> with "
- "Python <u>%s %dbit</u>:\n\n%s"
- % (
- dist.version,
- dist.architecture,
- "<br>".join(notcompatible),
- ),
- QMessageBox.Ok,
- )
- def add_package(self, package):
- for pack in self.model.packages:
- if pack.name == package.name:
- return
- self.model.packages.append(package)
- self.model.packages.sort(key=lambda x: x.name)
- self.model.checked.add(package)
- self.reset_model()
- def remove_package(self, package):
- self.model.packages = [
- pack
- for pack in self.model.packages
- if pack.fname != package.fname
- ]
- if package in self.model.checked:
- self.model.checked.remove(package)
- if package in self.model.actions:
- self.model.actions.pop(package)
- self.reset_model()
- def refresh_distribution(self, dist):
- self.distribution = dist
- if self.process == 'install':
- for package in self.model.packages:
- pack = dist.find_package(package.name)
- if pack is None:
- action = INSTALL_ACTION
- elif pack.version == package.version:
- if self.repair:
- action = REPAIR_ACTION
- else:
- action = NO_REPAIR_ACTION
- else:
- action = UPGRADE_ACTION + pack.version
- self.model.actions[package] = action
- else:
- self.model.packages = (
- self.distribution.get_installed_packages()
- )
- for package in self.model.packages:
- self.model.actions[package] = NONE_ACTION
- self.reset_model()
- def select_all(self):
- allpk = set(self.model.packages)
- if self.model.checked == allpk:
- self.model.checked = set()
- else:
- self.model.checked = allpk
- self.model.reset()
- def dragMoveEvent(self, event):
- """Reimplement Qt method, just to avoid default drag'n drop
- implementation of QTableView to handle events"""
- event.acceptProposedAction()
- def dragEnterEvent(self, event):
- """Reimplement Qt method
- Inform Qt about the types of data that the widget accepts"""
- source = event.mimeData()
- if source.hasUrls() and mimedata2url(source):
- event.acceptProposedAction()
- def dropEvent(self, event):
- """Reimplement Qt method
- Unpack dropped data and handle it"""
- source = event.mimeData()
- fnames = [
- path
- for path in mimedata2url(source)
- if osp.isfile(path)
- ]
- self.add_packages(fnames)
- event.acceptProposedAction()
- class DistributionSelector(QWidget):
- """Python distribution selector widget"""
- TITLE = 'Select a Python distribution path'
- # Signals after PyQt4 old SIGNAL removal
- selected_distribution = Signal(str)
- def __init__(self, parent):
- super(DistributionSelector, self).__init__(parent)
- self.browse_btn = None
- self.label = None
- self.line_edit = None
- self.setup_widget()
- def set_distribution(self, path):
- """Set distribution directory"""
- self.line_edit.setText(path)
- def setup_widget(self):
- """Setup workspace selector widget"""
- self.label = QLabel()
- self.line_edit = QLineEdit()
- self.line_edit.setAlignment(Qt.AlignRight)
- self.line_edit.setReadOnly(True)
- # self.line_edit.setDisabled(True)
- self.browse_btn = QPushButton(
- get_std_icon('DirOpenIcon'), "", self
- )
- self.browse_btn.setToolTip(self.TITLE)
- # PyQt4 old SIGNAL:self.connect(self.browse_btn, SIGNAL("clicked()"),
- # PyQt4 old SIGNAL: self.select_directory)
- self.browse_btn.clicked.connect(
- self.select_directory
- )
- layout = QHBoxLayout()
- layout.addWidget(self.label)
- layout.addWidget(self.line_edit)
- layout.addWidget(self.browse_btn)
- layout.setContentsMargins(0, 0, 0, 0)
- self.setLayout(layout)
- def select_directory(self):
- """Select directory"""
- basedir = to_text_string(self.line_edit.text())
- if not osp.isdir(basedir):
- basedir = getcwd()
- while True:
- directory = getexistingdirectory(
- self, self.TITLE, basedir
- )
- if not directory:
- break
- if not utils.is_python_distribution(directory):
- QMessageBox.warning(
- self,
- self.TITLE,
- "The following directory is not a Python distribution.",
- QMessageBox.Ok,
- )
- basedir = directory
- continue
- directory = osp.abspath(osp.normpath(directory))
- self.set_distribution(directory)
- # PyQt4 old SIGNAL: self.emit(SIGNAL('selected_distribution(QString)'), directory)
- self.selected_distribution.emit(directory)
- break
- class Thread(QThread):
- """Installation/Uninstallation thread"""
- def __init__(self, parent):
- QThread.__init__(self, parent)
- self.callback = None
- self.error = None
- def run(self):
- try:
- self.callback()
- except Exception as error:
- error_str = str(error)
- fs_encoding = (
- sys.getfilesystemencoding()
- or locale.getpreferredencoding()
- )
- try:
- error_str = error_str.decode(fs_encoding)
- except (
- UnicodeError,
- TypeError,
- AttributeError,
- ):
- pass
- self.error = error_str
- def python_distribution_infos():
- """Return Python distribution infos (not selected distribution but
- the one used to run this script)"""
- winpyver = os.environ.get('WINPYVER')
- if winpyver is None:
- return 'Unknown Python distribution'
- else:
- return 'WinPython ' + winpyver
- class PMWindow(QMainWindow):
- NAME = 'WinPython Control Panel'
- def __init__(self):
- QMainWindow.__init__(self)
- self.setAttribute(Qt.WA_DeleteOnClose)
- self.distribution = None
- self.tabwidget = None
- self.selector = None
- self.table = None
- self.untable = None
- self.basedir = None
- self.select_all_action = None
- self.install_action = None
- self.uninstall_action = None
- self.remove_action = None
- self.packages_icon = get_std_icon(
- 'FileDialogContentsView'
- )
- self.setup_window()
- def _add_table(self, table, title, icon):
- """Add table tab to main tab widget, return button layout"""
- widget = QWidget()
- tabvlayout = QVBoxLayout()
- widget.setLayout(tabvlayout)
- tabvlayout.addWidget(table)
- btn_layout = QHBoxLayout()
- tabvlayout.addLayout(btn_layout)
- self.tabwidget.addTab(widget, icon, title)
- return btn_layout
- def setup_window(self):
- """Setup main window"""
- self.setWindowTitle(self.NAME)
- self.setWindowIcon(get_icon('winpython.svg'))
- self.selector = DistributionSelector(self)
- # PyQt4 old SIGNAL: self.connect(self.selector, SIGNAL('selected_distribution(QString)'),
- # PyQt4 old SIGNAL: self.distribution_changed)
- self.selector.selected_distribution.connect(
- self.distribution_changed
- )
- self.table = PackagesTable(
- self, 'install', self.NAME
- )
- # PyQt4 old SIGNAL:self.connect(self.table, SIGNAL('package_added()'),
- # PyQt4 old SIGNAL: self.refresh_install_button)
- self.table.package_added.connect(
- self.refresh_install_button
- )
- # PyQt4 old SIGNAL: self.connect(self.table, SIGNAL("clicked(QModelIndex)"),
- # PyQt4 old SIGNAL: lambda index: self.refresh_install_button())
- self.table.clicked.connect(
- lambda index: self.refresh_install_button()
- )
- self.untable = PackagesTable(
- self, 'uninstall', self.NAME
- )
- # PyQt4 old SIGNAL:self.connect(self.untable, SIGNAL("clicked(QModelIndex)"),
- # PyQt4 old SIGNAL: lambda index: self.refresh_uninstall_button())
- self.untable.clicked.connect(
- lambda index: self.refresh_uninstall_button()
- )
- self.selector.set_distribution(sys.prefix)
- self.distribution_changed(sys.prefix)
- self.tabwidget = QTabWidget()
- # PyQt4 old SIGNAL:self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
- # PyQt4 old SIGNAL: self.current_tab_changed)
- self.tabwidget.currentChanged.connect(
- self.current_tab_changed
- )
- btn_layout = self._add_table(
- self.table,
- "Install/upgrade packages",
- get_std_icon("ArrowDown"),
- )
- unbtn_layout = self._add_table(
- self.untable,
- "Uninstall packages",
- get_std_icon("DialogResetButton"),
- )
- central_widget = QWidget()
- vlayout = QVBoxLayout()
- vlayout.addWidget(self.selector)
- vlayout.addWidget(self.tabwidget)
- central_widget.setLayout(vlayout)
- self.setCentralWidget(central_widget)
- # Install tab
- add_action = create_action(
- self,
- "&Add packages...",
- icon=get_std_icon('DialogOpenButton'),
- triggered=self.add_packages,
- )
- self.remove_action = create_action(
- self,
- "Remove",
- shortcut=keybinding('Delete'),
- icon=get_std_icon('TrashIcon'),
- triggered=self.remove_packages,
- )
- self.remove_action.setEnabled(False)
- self.select_all_action = create_action(
- self,
- "(Un)Select all",
- shortcut=keybinding('SelectAll'),
- icon=get_std_icon('DialogYesButton'),
- triggered=self.table.select_all,
- )
- self.install_action = create_action(
- self,
- "&Install packages",
- icon=get_std_icon('DialogApplyButton'),
- triggered=lambda: self.process_packages(
- 'install'
- ),
- )
- self.install_action.setEnabled(False)
- quit_action = create_action(
- self,
- "&Quit",
- icon=get_std_icon('DialogCloseButton'),
- triggered=self.close,
- )
- packages_menu = self.menuBar().addMenu("&Packages")
- add_actions(
- packages_menu,
- [
- add_action,
- self.remove_action,
- self.install_action,
- None,
- quit_action,
- ],
- )
- # Uninstall tab
- self.uninstall_action = create_action(
- self,
- "&Uninstall packages",
- icon=get_std_icon('DialogCancelButton'),
- triggered=lambda: self.process_packages(
- 'uninstall'
- ),
- )
- self.uninstall_action.setEnabled(False)
- uninstall_btn = action2button(
- self.uninstall_action,
- autoraise=False,
- text_beside_icon=True,
- )
- # Option menu
- option_menu = self.menuBar().addMenu("&Options")
- repair_action = create_action(
- self,
- "Repair packages",
- tip="Reinstall packages even if version is unchanged",
- toggled=self.toggle_repair,
- )
- add_actions(option_menu, (repair_action,))
- # Advanced menu
- option_menu = self.menuBar().addMenu("&Advanced")
- register_action = create_action(
- self,
- "Register distribution...",
- tip="Register file extensions, icons and context menu",
- triggered=self.register_distribution,
- )
- unregister_action = create_action(
- self,
- "Unregister distribution...",
- tip="Unregister file extensions, icons and context menu",
- triggered=self.unregister_distribution,
- )
- open_console_action = create_action(
- self,
- "Open console here",
- triggered=lambda: os.startfile(
- self.command_prompt_path
- ),
- )
- open_console_action.setEnabled(
- osp.exists(self.command_prompt_path)
- )
- add_actions(
- option_menu,
- (
- register_action,
- unregister_action,
- None,
- open_console_action,
- ),
- )
- # # View menu
- # view_menu = self.menuBar().addMenu("&View")
- # popmenu = self.createPopupMenu()
- # add_actions(view_menu, popmenu.actions())
- # Help menu
- about_action = create_action(
- self,
- "About %s..." % self.NAME,
- icon=get_std_icon('MessageBoxInformation'),
- triggered=self.about,
- )
- report_action = create_action(
- self,
- "Report issue...",
- icon=get_icon('bug.png'),
- triggered=self.report_issue,
- )
- help_menu = self.menuBar().addMenu("?")
- add_actions(
- help_menu, [about_action, None, report_action]
- )
- # Status bar
- status = self.statusBar()
- status.setObjectName("StatusBar")
- status.showMessage(
- "Welcome to %s!" % self.NAME, 5000
- )
- # Button layouts
- for act in (
- add_action,
- self.remove_action,
- None,
- self.select_all_action,
- self.install_action,
- ):
- if act is None:
- btn_layout.addStretch()
- else:
- btn_layout.addWidget(
- action2button(
- act,
- autoraise=False,
- text_beside_icon=True,
- )
- )
- unbtn_layout.addWidget(uninstall_btn)
- unbtn_layout.addStretch()
- self.resize(400, 500)
- def current_tab_changed(self, index):
- """Current tab has just changed"""
- if index == 0:
- self.show_drop_tip()
- def refresh_install_button(self):
- """Refresh install button enable state"""
- self.table.refresh_distribution(self.distribution)
- self.install_action.setEnabled(
- len(self.get_packages_to_be_installed()) > 0
- )
- nbp = len(self.table.get_selected_packages())
- for act in (
- self.remove_action,
- self.select_all_action,
- ):
- act.setEnabled(nbp > 0)
- self.show_drop_tip()
- def show_drop_tip(self):
- """Show drop tip on install table"""
- callback = lambda: QToolTip.showText(
- self.table.mapToGlobal(self.table.pos()),
- '<b>Drop files here</b><br>'
- 'Executable installers (distutils) or source packages',
- self,
- )
- QTimer.singleShot(500, callback)
- def refresh_uninstall_button(self):
- """Refresh uninstall button enable state"""
- nbp = len(self.untable.get_selected_packages())
- self.uninstall_action.setEnabled(nbp > 0)
- def toggle_repair(self, state):
- """Toggle repair mode"""
- self.table.repair = state
- self.refresh_install_button()
- def register_distribution(self):
- """Register distribution"""
- answer = QMessageBox.warning(
- self,
- "Register distribution",
- "This will associate file extensions, icons and "
- "Windows explorer's context menu entries ('Edit with IDLE', ...) "
- "with selected Python distribution in Windows registry. "
- "<br>Shortcuts for all WinPython launchers will be installed "
- "in <i>WinPython</i> Start menu group (replacing existing "
- "shortcuts)."
- "<br>If <i>pywin32</i> is installed (it should be on any "
- "WinPython distribution), the Python ActiveX Scripting client "
- "will also be registered."
- "<br><br><u>Warning</u>: the only way to undo this change is to "
- "register another Python distribution to Windows registry."
- "<br><br><u>Note</u>: these actions are exactly the same as those "
- "performed when installing Python with the official installer "
- "for Windows.<br><br>Do you want to continue?",
- QMessageBox.Yes | QMessageBox.No,
- )
- if answer == QMessageBox.Yes:
- associate.register(self.distribution.target)
- def unregister_distribution(self):
- """Unregister distribution"""
- answer = QMessageBox.warning(
- self,
- "Unregister distribution",
- "This will remove file extensions associations, icons and "
- "Windows explorer's context menu entries ('Edit with IDLE', ...) "
- "with selected Python distribution in Windows registry. "
- "<br>Shortcuts for all WinPython launchers will be removed "
- "from <i>WinPython</i> Start menu group."
- "<br>If <i>pywin32</i> is installed (it should be on any "
- "WinPython distribution), the Python ActiveX Scripting client "
- "will also be unregistered."
- "<br><br>Do you want to continue?",
- QMessageBox.Yes | QMessageBox.No,
- )
- if answer == QMessageBox.Yes:
- associate.unregister(self.distribution.target)
- @property
- def command_prompt_path(self):
- return osp.join(
- self.distribution.target,
- osp.pardir,
- "WinPython Command Prompt.exe",
- )
- def distribution_changed(self, path):
- """Distribution path has just changed"""
- for package in self.table.model.packages:
- self.table.remove_package(package)
- dist = wppm.Distribution(to_text_string(path))
- self.table.refresh_distribution(dist)
- self.untable.refresh_distribution(dist)
- self.distribution = dist
- self.selector.label.setText(
- 'Python %s %dbit:'
- % (dist.version, dist.architecture)
- )
- def add_packages(self):
- """Add packages"""
- basedir = (
- self.basedir if self.basedir is not None else ''
- )
- fnames, _selfilter = getopenfilenames(
- parent=self,
- basedir=basedir,
- caption='Add packages',
- filters='*.exe *.zip *.tar.gz *.whl',
- )
- if fnames:
- self.basedir = osp.dirname(fnames[0])
- self.table.add_packages(fnames)
- def get_packages_to_be_installed(self):
- """Return packages to be installed"""
- return [
- pack
- for pack in self.table.get_selected_packages()
- if self.table.model.actions[pack]
- not in (NO_REPAIR_ACTION, NONE_ACTION)
- ]
- def remove_packages(self):
- """Remove selected packages"""
- for package in self.table.get_selected_packages():
- self.table.remove_package(package)
- def process_packages(self, action):
- """Install/uninstall packages"""
- if action == 'install':
- text, table = 'Installing', self.table
- if not self.get_packages_to_be_installed():
- return
- elif action == 'uninstall':
- text, table = 'Uninstalling', self.untable
- else:
- raise AssertionError
- packages = table.get_selected_packages()
- if not packages:
- return
- func = getattr(self.distribution, action)
- thread = Thread(self)
- for widget in self.children():
- if isinstance(widget, QWidget):
- widget.setEnabled(False)
- try:
- status = self.statusBar()
- except AttributeError:
- status = self.parent().statusBar()
- progress = QProgressDialog(
- self, Qt.FramelessWindowHint
- )
- progress.setMaximum(
- len(packages)
- ) # old vicious bug:len(packages)-1
- for index, package in enumerate(packages):
- progress.setValue(index)
- progress.setLabelText(
- "%s %s %s..."
- % (text, package.name, package.version)
- )
- QApplication.processEvents()
- if progress.wasCanceled():
- break
- if package in table.model.actions:
- try:
- thread.callback = lambda: func(package)
- thread.start()
- while thread.isRunning():
- QApplication.processEvents()
- if progress.wasCanceled():
- status.setEnabled(True)
- status.showMessage(
- "Cancelling operation..."
- )
- table.remove_package(package)
- error = thread.error
- except Exception as error:
- error = to_text_string(error)
- if error is not None:
- pstr = (
- package.name + ' ' + package.version
- )
- QMessageBox.critical(
- self,
- "Error",
- "<b>Unable to %s <i>%s</i></b>"
- "<br><br>Error message:<br>%s"
- % (action, pstr, error),
- )
- progress.setValue(progress.maximum())
- status.clearMessage()
- for widget in self.children():
- if isinstance(widget, QWidget):
- widget.setEnabled(True)
- thread = None
- for table in (self.table, self.untable):
- table.refresh_distribution(self.distribution)
- def report_issue(self):
- issue_template = """\
- Python distribution: %s
- Control panel version: %s
- Python Version: %s
- Qt Version: %s, %s %s
- What steps will reproduce the problem?
- 1.
- 2.
- 3.
- What is the expected output? What do you see instead?
- Please provide any additional information below.
- """ % (
- python_distribution_infos(),
- __version__,
- platform.python_version(),
- winpython._vendor.qtpy.QtCore.__version__,
- winpython.qt.API_NAME,
- winpython._vendor.qtpy.__version__,
- )
- url = QUrl("%s/issues/entry" % __project_url__)
- url.addQueryItem("comment", issue_template)
- QDesktopServices.openUrl(url)
- def about(self):
- """About this program"""
- QMessageBox.about(
- self,
- "About %s" % self.NAME,
- """<b>%s %s</b>
- <br>Package Manager and Advanced Tasks
- <p>Copyright © 2012 Pierre Raybaut
- <br>Licensed under the terms of the MIT License
- <p>Created, developed and maintained by Pierre Raybaut
- <p><a href="%s">WinPython at Github.io</a>: downloads, bug reports,
- discussions, etc.</p>
- <p>This program is executed by:<br>
- <b>%s</b><br>
- Python %s, Qt %s, %s qtpy %s"""
- % (
- self.NAME,
- __version__,
- __project_url__,
- python_distribution_infos(),
- platform.python_version(),
- winpython._vendor.qtpy.QtCore.__version__,
- winpython._vendor.qtpy.API_NAME,
- winpython._vendor.qtpy.__version__,
- ),
- )
- def main(test=False):
- app = QApplication([])
- win = PMWindow()
- win.show()
- if test:
- return app, win
- else:
- app.exec_()
- def test():
- app, win = main(test=True)
- print(sys.modules)
- app.exec_()
- if __name__ == "__main__":
- main()
|