Ignore:
Timestamp:
11/13/12 20:05:23 (17 months ago)
Author:
Matija Polajnar <matija.polajnar@…>
Branch:
default
Message:

Rewrite the add-on support modules and GUI to support the new properly packed add-ons, published on PyPI.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/orngDlgs.py

    r10778 r11018  
    675675 
    676676class AddOnManagerSummary(QDialog): 
    677     def __init__(self, add, remove, *args): 
     677    def __init__(self, add, remove, upgrade, *args): 
    678678        apply(QDialog.__init__,(self,) + args) 
    679679        self.setWindowTitle("Pending Actions") 
     
    697697                        lambda docSize: self.updateMinSize(docSize)) 
    698698        actions = [] 
    699         for addOnId in add: 
    700             if addOnId in remove: 
    701                 actions.append("Upgrade %s." % add[addOnId].name) 
    702             elif addOnId.startswith("registered:"): 
    703                 actions.append("Register %s." % add[addOnId].name) 
    704             else: 
    705                 actions.append("Install %s." % add[addOnId].name) 
    706         for addOnId in remove: 
    707             if not addOnId in add: 
    708                 if addOnId.startswith("registered:"): 
    709                     actions.append("Unregister %s." % remove[addOnId].name) 
    710                 else: 
    711                     actions.append("Remove %s." % remove[addOnId].name) 
    712         actions.sort() 
     699        for ao in add: 
     700            actions.append("Install %s." % ao) 
     701        for ao in remove: 
     702            actions.append("Remove %s." % ao) 
     703        for ao in upgrade: 
     704            actions.append("Upgrade %s." % ao) 
    713705        memo.setText("\n".join(actions)) 
    714706         
     
    726718        self.memo.setMinimumHeight(min(300, documentSize.height() + 2 * self.memo.frameWidth())) 
    727719 
    728  
    729 class AddOnRepositoryData(QDialog): 
    730     def __init__(self, name="", url="", *args): 
    731         apply(QDialog.__init__,(self,) + args) 
    732         self.setWindowTitle("Add-on Repository") 
    733         self.topLayout = QVBoxLayout(self) 
    734         self.topLayout.setSpacing(0) 
    735          
    736         self.name = name 
    737         self.url = url      
    738          
    739         eName = OWGUI.lineEdit(self, self, "name", "Display name:", orientation="horizontal", controlWidth=150) 
    740         eName.parent().layout().addStretch(1) 
    741         eURL = OWGUI.lineEdit(self, self, "url", "URL:", orientation="horizontal", controlWidth=250) 
    742         eURL.parent().layout().addStretch(1) 
    743         self.layout().addSpacing(15) 
    744         hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)) 
    745         hbox.layout().addStretch(1) 
    746         self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept) 
    747         self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject) 
    748         self.okButton.setDefault(True) 
    749          
    750     def accept(self): 
    751         if self.name.strip() == "": 
    752             QMessageBox.warning(self, "Incorrect Input", "Name cannot be empty") 
    753             return 
    754         if self.url.strip() == "": 
    755             QMessageBox.warning(self, "Incorrect Input", "URL cannot be empty") 
    756             return 
    757         QDialog.accept(self) 
    758          
    759          
    760720class AddOnManagerDialog(QDialog): 
    761721    def __init__(self, canvasDlg, *args): 
     
    773733         
    774734        self.groupByRepo = True 
    775         self.sortInstalledFirst = True 
    776         self.sortSingleLast = True 
    777735        self.searchStr = "" 
     736        self.to_upgrade = set() 
    778737 
    779738        searchBox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) 
    780739 
    781         self.viewBox = viewBox = OWGUI.SmallWidgetLabel(searchBox, pixmap = 1, box = "Grouping and Order", tooltip = "Adjust the order of add-ons in the list") 
    782         cGroupByRepo        = OWGUI.checkBox(viewBox.widget, self, "groupByRepo", "&Group by repository", callback = self.refreshView) 
    783         cSortInstalledFirst = OWGUI.checkBox(viewBox.widget, self, "sortInstalledFirst", "&Installed first", callback = self.refreshView) 
    784         cSortSingleLast     = OWGUI.checkBox(viewBox.widget, self, "sortSingleLast", "&Single widgets last", callback = self.refreshView) 
    785  
    786740        self.eSearch = self.lineEditSearch(searchBox, self, "searchStr", None, 0, tooltip = "Type in to filter (search) add-ons.", callbackOnType=True, callback=self.searchCallback) 
    787741         
    788742        # Repository & Add-on tree 
    789743         
    790         repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) 
     744        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) 
    791745        repos.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    792         self.tree = tree = QTreeWidget(repos) 
    793         self.tree.setMinimumWidth(200) 
    794         self.tree.repoItems = {} 
    795         tree.header().hide() 
    796         repos.layout().addWidget(tree) 
    797         QObject.connect(tree, SIGNAL("itemChanged(QTreeWidgetItem *, int)"), self.cbToggled) 
    798         QObject.connect(tree, SIGNAL("currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)"), self.currentItemChanged) 
    799  
    800         self.addOnsToAdd = {} 
    801         self.addOnsToRemove = {} 
     746        self.lst = lst = QListWidget(repos) 
     747        lst.setMinimumWidth(200) 
     748        lst.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) 
     749        repos.layout().addWidget(lst) 
     750        QObject.connect(lst, SIGNAL("itemChanged(QListWidgetItem *)"), self.cbToggled) 
     751        QObject.connect(lst, SIGNAL("currentItemChanged(QListWidgetItem *, QListWidgetItem *)"), self.currentItemChanged) 
     752 
    802753        import Orange.utils.addons 
    803         self.repositories = [repo.clone() for repo in Orange.utils.addons.available_repositories] 
    804          
     754 
    805755        # Bottom info pane 
    806756         
    807         self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) 
     757        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) 
    808758        infoPane.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    809759 
     
    833783        pInfoBtns = OWGUI.widgetBox(infoPane, orientation="horizontal", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)) 
    834784        self.webButton = OWGUI.button(pInfoBtns, self, "Open webpage", callback = self.openWebPage) 
     785        self.docButton = OWGUI.button(pInfoBtns, self, "Open documentation", callback = self.openDocsPage) 
    835786        self.listWidgetsButton = OWGUI.button(pInfoBtns, self, "List widgets", callback = self.listWidgets) 
    836787        pInfoBtns.layout().addStretch(1) 
     
    859810        rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)) 
    860811        rightPanel.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    861         self.addRepoButton = OWGUI.button(rightPanel, self, "Add Repository...", callback = self.addRepo) 
    862         self.editRepoButton = OWGUI.button(rightPanel, self, "Edit Repository...", callback = self.editRepo) 
    863         self.delRepoButton = OWGUI.button(rightPanel, self, "Remove Repository", callback = self.delSelectedRepo) 
    864         self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh lists", callback = self.reloadRepos) 
     812        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh list", callback = self.reloadRepo) 
    865813        rightPanel.layout().addSpacing(15) 
    866814        self.upgradeAllButton = OWGUI.button(rightPanel, self, "Upgrade All", callback = self.upgradeAll) 
    867         rightPanel.layout().addSpacing(15) 
    868         self.registerButton = OWGUI.button(rightPanel, self, "Register Add-on...", callback = self.registerAddOn) 
    869815        rightPanel.layout().addStretch(1) 
    870816        for btn in rightPanel.children(): 
     
    887833     
    888834    def accept(self): 
     835        self.to_upgrade.difference_update(self.to_remove()) 
    889836        import Orange.utils.addons 
    890         if len(self.addOnsToAdd) + len(self.addOnsToRemove) > 0: 
    891             summary = AddOnManagerSummary(self.addOnsToAdd, self.addOnsToRemove, self) 
     837        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade 
     838        if len(add) + len(remove) + len(upgrade) > 0: 
     839            summary = AddOnManagerSummary(add, remove, upgrade, self) 
    892840            if summary.exec_() == QDialog.Rejected: 
    893841                return 
    894         Orange.utils.addons.available_repositories = self.repositories 
    895         Orange.utils.addons.save_repositories() 
    896842        QDialog.accept(self) 
    897843         
    898     def addRepo(self): 
    899         dlg = AddOnRepositoryData() 
    900         while dlg.exec_() == QDialog.Accepted: 
    901             import Orange.utils.addons 
    902             try: 
    903                 repo = Orange.utils.addons.OrangeAddOnRepository(dlg.name, dlg.url)   #TODO: This can take some time - inform the user! 
    904                 self.repositories.append(repo) 
    905             except Exception, e: 
    906                 QMessageBox.critical(self, "Error", "Could not add this repository: %s"%e) 
    907                 continue 
    908             break 
    909         self.refreshView() 
    910  
    911     def editRepo(self, repo=None): 
    912         if not repo: 
    913             repo = self.getRepoFromItem(self.tree.currentItem()) 
    914         if not repo: 
    915             return 
    916         dlg = AddOnRepositoryData(name=repo.name, url=repo.url) 
    917         while dlg.exec_() == QDialog.Accepted: 
    918             import Orange.utils.addons 
    919             try: 
    920                 oldname, oldurl = repo.name, repo.url 
    921                 repo.name, repo.url = dlg.name, dlg.url 
    922                 if oldurl != repo.url: 
    923                     repo.refreshdata(force=True)  #TODO: This can take some time - inform the user! 
    924             except Exception, e: 
    925                 repo.name, repo.url = oldname, oldurl 
    926                 QMessageBox.critical(self, "Error", "Could not load repository %s."%e) 
    927                 continue 
    928             break 
    929         self.refreshView() 
    930  
    931     def delSelectedRepo(self): 
    932         repo = self.getRepoFromItem(self.tree.currentItem()) 
    933         if repo==None: 
    934             return 
    935         # Is it a default repository? We cannot delete it! 
     844    def reloadRepo(self): 
     845        # Reload add-on list. 
     846        # TODO: This can take some time - show some progress to user! 
    936847        import Orange.utils.addons 
    937         if repo.__class__ is Orange.utils.addons.OrangeDefaultAddOnRepository: 
    938             return 
    939          
    940         # Are there add-ons selected for installation from this repository? We remove the installation requests. 
    941         for (id, addOn) in self.addOnsToAdd.items(): 
    942             if addOn.repository == repo: 
    943                 del self.addOnsToAdd[id] 
    944          
    945         # Remove the repository and refresh tree. 
    946         self.repositories.remove(repo) 
    947         self.refreshView() 
    948      
    949     def reloadRepos(self): 
    950         # Reload add-on list for all repositories. 
    951         # TODO: This can take some time - show some progress to user! 
    952         for repo in self.repositories: 
    953             try: 
    954                 repo.refreshdata(force=True) 
    955             except Exception, e:  # Maybe gather all exceptions (for all repositories) and show them in the end? 
    956                 QMessageBox.critical(self, "Error", "Could not reload repository '%s': %s." % (repo.name, e)) 
    957         # Were any installation-pending add-ons removed from repositories? 
    958         for (id, addOn) in self.addOnsToAdd.items(): 
    959             if id in addOn.repository.addons: 
    960                 newObject = [version for version in addOn.repository.addons[id] if version.version == addOn.version] 
    961                 if newObject != []: 
    962                     self.addOnsToAdd[id] = newObject[0] 
    963                     continue 
    964             del self.addOnsToAdd[id] 
    965             if id in self.addOnsToRemove:    # If true, it was a request for upgrade, not installation -- do not remove the installed version! 
    966                 del self.addOnsToRemove[id] 
     848        try: 
     849            Orange.utils.addons.refresh_available_addons() 
     850        except Exception, e:  # Maybe gather all exceptions (for all repositories) and show them in the end? 
     851            QMessageBox.critical(self, "Error", "Could not reload repository '%s': %s." % (repo.name, e)) 
    967852        # Finally, refresh the tree on GUI. 
    968853        self.refreshView() 
     
    970855    def upgradeCandidates(self): 
    971856        result = [] 
    972         import orngEnviron, Orange.utils.addons 
    973         for item in self.tree.addOnItems: 
    974             id = item.newest.id 
    975             if id.startswith("registered:"): continue 
    976             installedAo = Orange.utils.addons.installed_addons[id] if id in Orange.utils.addons.installed_addons else None 
    977             installed = installedAo.version if installedAo else None  
    978             selected = self.addOnsToAdd[id].version if id in self.addOnsToAdd else None 
    979             if installed: 
    980                 if installedAo.directory.startswith(orngEnviron.addOnsDirUser): 
    981                     if installed < item.newest.version: 
    982                         if selected: 
    983                             if selected >= item.newest.version: 
    984                                 continue 
    985                         result.append(item.newest) 
     857        import Orange.utils.addons 
     858        for ao in Orange.utils.addons.addons.values(): 
     859            if ao.installed_version and ao.available_version and ao.installed_version != ao.available_version: 
     860                result.append(ao.name) 
    986861        return result 
    987862     
     
    990865            self.upgrade(candidate, refresh=False) 
    991866        self.refreshInfoPane() 
    992         self.enableDisableButtons() 
    993          
    994     def upgrade(self, newAddOn=None, refresh=True): 
    995         if not newAddOn: 
    996             newAddOn = self.getAddOnFromItem(self.tree.currentItem()) 
    997         if not newAddOn: 
    998             return 
    999         import Orange.utils.addons 
    1000         self.addOnsToRemove[newAddOn.id] = Orange.utils.addons.installed_addons[newAddOn.id] 
    1001         self.addOnsToAdd[newAddOn.id] = newAddOn 
     867 
     868    def upgrade(self, name=None, refresh=True): 
     869        if not name: 
     870            name = self.getAddOnIdFromItem(self.lst.currentItem()) 
     871        self.to_upgrade.add(name) 
    1002872        if refresh: 
    1003873            self.refreshInfoPane() 
    1004             self.enableDisableButtons() 
    1005  
    1006     def registerAddOn(self): 
    1007         dir = str(QFileDialog.getExistingDirectory(self, "Select the folder that contains the add-on:")) 
    1008         if dir != "": 
    1009             if os.path.split(dir)[1] == "widgets":     # register a dir above the dir that contains the widget folder 
    1010                 dir = os.path.split(dir)[0] 
    1011             if os.path.exists(os.path.join(dir, "widgets")): 
    1012                 name = os.path.split(dir)[1] 
    1013                 import Orange.utils.addons 
    1014                 id = "registered:"+dir 
    1015                 self.addOnsToAdd[id] = Orange.utils.addons.OrangeRegisteredAddOn(name, dir, systemwide=False) 
    1016                 self.refreshView(id) 
    1017             else: 
    1018                 QMessageBox.information( None, "Information", 'The specified folder does not seem to contain an Orange add-on.', QMessageBox.Ok + QMessageBox.Default) 
    1019874 
    1020875    def openWebPage(self): 
    1021         addOn = self.getAddOnFromItem(self.tree.currentItem()) 
    1022         if not addOn: return 
    1023         if not addOn.homepage: return 
    1024         import webbrowser 
    1025         webbrowser.open(addOn.homepage) 
    1026          
     876        addon = self.getAddOnFromItem(self.lst.currentItem()) 
     877        if addon and addon.homepage: 
     878            import webbrowser 
     879            webbrowser.open(addon.homepage) 
     880 
     881    def openDocsPage(self): 
     882        addon = self.getAddOnFromItem(self.lst.currentItem()) 
     883        if addon and addon.docs_url: 
     884            import webbrowser 
     885            webbrowser.open(addon.docs_url) 
     886 
    1027887    def listWidgets(self): 
    1028         addOn = self.getAddOnFromItem(self.tree.currentItem()) 
     888        addOn = self.getAddOnFromItem(self.lst.currentItem()) 
    1029889        if not addOn: return 
    1030890        import Orange.utils.addons 
     
    1034894         
    1035895         
    1036     def donotUpgrade(self, newAddOn=None): 
    1037         if not newAddOn: 
    1038             newAddOn = self.getAddOnFromItem(self.tree.currentItem()) 
    1039         if not newAddOn: 
    1040             return 
    1041         del self.addOnsToAdd[newAddOn.id] 
    1042         del self.addOnsToRemove[newAddOn.id] 
     896    def donotUpgrade(self): 
     897        id = self.getAddOnIdFromItem(self.lst.currentItem()) 
     898        self.to_upgrade.remove(id) 
    1043899        self.refreshInfoPane() 
    1044          
     900 
     901    def cbToggled(self, item): 
     902        self.refreshInfoPane(item) 
     903 
    1045904    def lineEditSearch(self, *args, **props): 
    1046905        return OWGUI.lineEdit(*args, **props) 
    1047906 
    1048     def cbToggled(self, item, column): 
    1049         # Not a request from an add-on item in tree? 
    1050         if (column != 0) or "disableToggleSignal" not in item.__dict__: 
    1051             return 
    1052         # Toggle signal currently disabled? 
    1053         if item.disableToggleSignal: 
    1054             return 
    1055          
    1056         addOn = item.newest 
    1057         id = addOn.id 
    1058         if item.checkState(0) == Qt.Checked:  # Mark for installation (or delete removal request) 
    1059             if id not in self.addOnsToAdd: 
    1060                 if id in self.addOnsToRemove: 
    1061                     del self.addOnsToRemove[id] 
    1062                 else: 
    1063                     self.addOnsToAdd[id] = addOn 
    1064         else:                                 # Mark for removal (or delete installation request) 
    1065             import Orange.utils.addons, orngEnviron 
    1066             installedAo = Orange.utils.addons.installed_addons[id] if id in Orange.utils.addons.installed_addons else None  
    1067             if installedAo: 
    1068                 if not installedAo.directory.startswith(orngEnviron.addOnsDirUser): 
    1069                     item.disableToggleSignal = True 
    1070                     item.setCheckState(0, Qt.Checked) 
    1071                     item.disableToggleSignal = False 
    1072                     return 
    1073             if id in self.addOnsToAdd: 
    1074                 del self.addOnsToAdd[id] 
    1075             elif id not in self.addOnsToRemove: 
    1076                 import Orange.utils.addons 
    1077                 if id in Orange.utils.addons.installed_addons: 
    1078                     self.addOnsToRemove[id] = Orange.utils.addons.installed_addons[id] 
    1079                 elif id.startswith("registered:"): 
    1080                     self.addOnsToRemove[id] = item.newest 
    1081         self.resetChecked(id)   # Refresh all checkboxes for this add-on (it might be in multiple repositories!) 
    1082         self.refreshInfoPane(item) 
    1083          
    1084     def getRepoFromItem(self, item): 
    1085         if not item: 
    1086             return None 
    1087         import Orange.utils.addons 
    1088         if hasattr(item, "repository"): 
    1089             return item.repository 
    1090         else: 
    1091             if item.newest.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: 
    1092                 return None 
    1093             return  item.newest.repository 
    1094      
    1095     def getAddOnFromItem(self, item):         
    1096         if hasattr(item, "newest"): 
    1097             return item.newest 
    1098         return None 
     907    def getAddOnFromItem(self, item): 
     908        return getattr(item, "addon", None) 
    1099909 
    1100910    def getAddOnIdFromItem(self, item): 
    1101         addOn = self.getAddOnFromItem(item)         
    1102         return addOn.id if addOn else None 
     911        addon = self.getAddOnFromItem(item) 
     912        return addon.name if addon else None 
    1103913         
    1104914    def refreshInfoPane(self, item=None): 
    1105915        if not item: 
    1106             item = self.tree.currentItem() 
    1107         import Orange.utils.addons 
    1108         if hasattr(item, "newest"): 
    1109             if item.newest.__class__ is not Orange.utils.addons.OrangeRegisteredAddOn: 
    1110                 import orngEnviron 
    1111                 addOn = item.newest 
    1112                 self.lblDescription.setText(addOn.description.strip() if addOn else "") 
    1113                 self.lblVerAvailValue.setText(addOn.version_str) 
    1114      
    1115                 addOnInstalled = Orange.utils.addons.installed_addons[addOn.id] if addOn.id in Orange.utils.addons.installed_addons else None 
    1116                 addOnToInstall = self.addOnsToAdd[addOn.id] if addOn.id in self.addOnsToAdd else None 
    1117                 addOnToRemove = self.addOnsToRemove[addOn.id] if addOn.id in self.addOnsToRemove else None 
    1118                  
    1119                 self.lblVerInstalledValue.setText((addOnInstalled.version_str+("" if addOnInstalled.directory.startswith(orngEnviron.addOnsDirUser) else " (installed system-wide)")) if addOnInstalled else "-") 
    1120                 self.upgradeButton.setVisible(addOnInstalled!=None and addOnInstalled.version < addOn.version and addOnToInstall!=addOn and addOnInstalled.directory.startswith(orngEnviron.addOnsDirUser)) 
    1121                 self.donotUpgradeButton.setVisible(addOn.id in self.addOnsToRemove and addOnToInstall==addOn) 
    1122                 self.webButton.setVisible(addOn.homepage != None) 
    1123                 self.listWidgetsButton.setVisible(len(addOn.widgets) > 0 and addOn.__class__ is Orange.utils.addons.OrangeAddOnInRepo and addOn.repository.has_web_script) 
    1124                  
    1125                 if addOnToInstall: 
    1126                     if addOnToRemove: self.lblStatus.setText("marked for upgrade") 
    1127                     else: self.lblStatus.setText("marked for installation") 
    1128                 elif addOnToRemove: self.lblStatus.setText("marked for removal") 
    1129                 else: self.lblStatus.setText("") 
    1130      
    1131                 self.infoPane.setVisible(True) 
    1132                 self.regiInfoPane.setVisible(False) 
     916            item = self.lst.currentItem() 
     917        addon = None 
     918        if item: 
     919            import Orange.utils.addons 
     920            import orngEnviron 
     921            addon = self.getAddOnFromItem(item) 
     922        if addon: 
     923            self.lblDescription.setText(addon.summary.strip() +"\n"+ addon.description.strip()) 
     924            self.lblVerAvailValue.setText(addon.available_version) 
     925 
     926            self.lblVerInstalledValue.setText(addon.installed_version if addon.installed_version else "-") #TODO Tell whether it's a system-wide installation 
     927            self.upgradeButton.setVisible(bool(addon.installed_version and addon.installed_version!=addon.available_version) and addon.name not in self.to_upgrade) #TODO Disable if it's a system-wide installation 
     928            self.donotUpgradeButton.setVisible(addon.name in self.to_upgrade) 
     929            self.webButton.setVisible(bool(addon.homepage)) 
     930            self.docButton.setVisible(bool(addon.docs_url)) 
     931            self.listWidgetsButton.setVisible(False) #TODO A list of widgets is not available 
     932 
     933            if not addon.installed_version and item.checkState()==Qt.Checked: 
     934                self.lblStatus.setText("marked for installation") 
     935            elif addon.installed_version and item.checkState()!=Qt.Checked: 
     936                self.lblStatus.setText("marked for removal") 
     937            elif addon.name in self.to_upgrade: 
     938                self.lblStatus.setText("marked for upgrade") 
    1133939            else: 
    1134                 self.lblRegisteredAddOnInfo.setText("This add-on is registered "+("system-wide." if item.newest.systemwide else "by user.")) 
    1135                 self.infoPane.setVisible(False) 
    1136                 self.regiInfoPane.setVisible(True) 
     940                self.lblStatus.setText("") 
     941 
     942            self.infoPane.setVisible(True) 
     943            self.regiInfoPane.setVisible(False) 
    1137944        else: 
    1138945            self.infoPane.setVisible(False) 
    1139946            self.regiInfoPane.setVisible(False) 
    1140          
     947        self.enableDisableButtons() 
     948 
    1141949    def enableDisableButtons(self): 
    1142         repo = self.getRepoFromItem(self.tree.currentItem()) 
    1143950        import Orange.utils.addons 
    1144         self.delRepoButton.setEnabled(repo.__class__ is not Orange.utils.addons.OrangeDefaultAddOnRepository if repo!=None else False) 
    1145         self.editRepoButton.setEnabled(repo.__class__ is not Orange.utils.addons.OrangeDefaultAddOnRepository if repo!=None else False) 
    1146         self.upgradeAllButton.setEnabled(self.upgradeCandidates() != []) 
     951        aos = Orange.utils.addons.addons.values() 
     952        self.upgradeAllButton.setEnabled(any(ao.installed_version and ao.available_version and 
     953                                             ao.installed_version != ao.available_version and 
     954                                             ao.name not in self.to_upgrade for ao in aos)) 
    1147955         
    1148956    def currentItemChanged(self, new, previous): 
    1149         # Enable/disable buttons 
    1150         self.enableDisableButtons() 
    1151              
    1152         # Refresh info pane 
     957        # Refresh info pane & button states 
    1153958        self.refreshInfoPane(new) 
    1154      
    1155     def resetChecked(self, id): 
    1156         import Orange.utils.addons 
    1157         value = id in Orange.utils.addons.installed_addons or id.startswith("registered:") 
    1158         value = value and id not in self.addOnsToRemove 
    1159         value = value or id in self.addOnsToAdd 
    1160         for treeItem in self.tree.addOnItems: 
    1161             if treeItem.newest.id == id: 
    1162                 treeItem.disableToggleSignal = True 
    1163                 treeItem.setCheckState(0,Qt.Checked if value else Qt.Unchecked); 
    1164                 treeItem.disableToggleSignal = False 
    1165  
    1166     def addAddOnsToTree(self, repoItem, addOnDict, insertToBeginning=False): 
    1167         # Transform dictionary {id->[versions]} list of tuples (newest,[otherVersions]) 
    1168         if type(addOnDict) is list: 
    1169             addOnList = [(ao, []) for ao in addOnDict] 
    1170         else: 
    1171             addOnList = [] 
    1172             for id in addOnDict: 
    1173                 versions = list(addOnDict[id])  # We make a copy, so that we can change it! 
    1174                 newest = versions[0] 
    1175                 for v in versions: 
    1176                     if v.version > newest.version: 
    1177                         newest = v 
    1178                 versions.remove(newest) 
    1179                 addOnList.append( (newest, versions) ) 
     959 
     960    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]): 
    1180961        # Sort alphabetically 
    1181         addOnList.sort(key=lambda (newest, versions): newest.name) 
    1182         # Single-addon packages last 
    1183         if self.sortSingleLast: 
    1184             addOnList = [(n, v) for (n, v) in addOnList if not n.has_single_widget] \ 
    1185                       + [(n, v) for (n, v) in addOnList if     n.has_single_widget] 
    1186         # Installed first 
    1187         if self.sortInstalledFirst and len(addOnList)>0 and "id" in addOnList[0][0].__dict__: 
    1188             import Orange.utils.addons 
    1189             addOnList = [(n, v) for (n, v) in addOnList if     n.id in Orange.utils.addons.installed_addons] \ 
    1190                       + [(n, v) for (n, v) in addOnList if not n.id in Orange.utils.addons.installed_addons] 
    1191          
    1192         for (i, (newest, versions)) in enumerate(addOnList): 
    1193             addOnItem = QTreeWidgetItem(repoItem if not insertToBeginning else None) 
    1194             if insertToBeginning: 
    1195                 if repoItem.__class__ is QTreeWidget: 
    1196                     repoItem.insertTopLevelItem(i, addOnItem) 
    1197                 else: 
    1198                     repoItem.insertChild(i, addOnItem) 
    1199             addOnItem.disableToggleSignal = True 
    1200             addOnItem.setText(0, newest.name) 
    1201             if newest.has_single_widget(): 
    1202                 italFont = QFont(addOnItem.font(0)) 
    1203                 italFont.setItalic(True) 
    1204                 addOnItem.setFont(0, italFont) 
    1205             addOnItem.setCheckState(0,Qt.Unchecked); 
    1206             addOnItem.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled) 
    1207             addOnItem.newest = newest 
    1208             addOnItem.otherVersions = versions 
    1209  
    1210             self.tree.addOnItems.append(addOnItem) 
    1211             repoItem.addOnItemsDict[newest.id] = addOnItem 
    1212             self.resetChecked(newest.id) 
    1213             addOnItem.disableToggleSignal = False 
    1214              
    1215  
    1216     def addRepositoryToTree(self, repo): 
    1217         repoItem = QTreeWidgetItem(self.tree) 
    1218         repoItem.repository = repo 
    1219         repoItem.addOnItemsDict = {} 
    1220         repoItem.setText(0, repo.name) 
    1221         boldFont = QFont(repoItem.font(0)) 
    1222         boldFont.setWeight(QFont.Bold) 
    1223         repoItem.setFont(0, boldFont) 
    1224         self.tree.repoItems[repo] = repoItem 
    1225          
    1226         addOnsToAdd = {} 
    1227         visibleAddOns = repo.search_index(self.searchStr) 
    1228         for (id, versions) in repo.addons.items(): 
    1229             if id in visibleAddOns: 
    1230                 addOnsToAdd[id] = versions 
    1231         self.addAddOnsToTree(repoItem, addOnsToAdd) 
    1232          
    1233         return repoItem 
    1234              
     962        addons = sorted(list(addon_dict.items()), 
     963                        key = lambda (name, ao): name) 
     964 
     965        for (i, (name, ao)) in enumerate(addons): 
     966            item = QListWidgetItem() 
     967            self.lst.addItem(item) 
     968            item.setText(name) 
     969            item.setCheckState(Qt.Checked if ao.installed_version and not name in to_remove or name in to_install else Qt.Unchecked) 
     970            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled) 
     971            item.addon = ao 
     972            if name == selected: 
     973                self.lst.setCurrentItem(item) 
     974 
     975            item.disableToggleSignal = False 
     976 
     977    def lst_items(self): 
     978        for i in xrange(self.lst.count()): 
     979            yield self.lst.item(i) 
     980 
     981    def to_install(self): 
     982        return set([item.addon.name for item in self.lst_items() 
     983                    if item.checkState()==Qt.Checked and not item.addon.installed_version]) 
     984 
     985    def to_remove(self): 
     986        return set([item.addon.name for item in self.lst_items() 
     987                    if item.checkState()!=Qt.Checked and item.addon.installed_version]) 
    1235988 
    1236989    def refreshView(self, selectedRegisteredAddOnId=None): 
    1237         # Save repository items expanded state 
    1238         expandedRepos = set([]) 
    1239         for (repo, item) in self.tree.repoItems.items(): 
    1240             if item.isExpanded(): 
    1241                 expandedRepos.add(repo) 
     990        import Orange 
    1242991        # Save current item selection 
    1243         selectedRepository = self.getRepoFromItem(self.tree.currentItem()) 
    1244         selectedAddOnId = self.getAddOnIdFromItem(self.tree.currentItem()) 
     992        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem()) 
     993        to_install = self.to_install() 
     994        to_remove = self.to_remove() 
    1245995        #TODO: Save the next repository selection too, in case the current one was deleted 
    1246996 
    1247997        # Clear the tree 
    1248         self.tree.repoItems = {} 
    1249         self.tree.addOnItems = [] 
    1250         self.tree.addOnItemsDict = {} 
    1251         self.tree.clear() 
    1252          
    1253         # Set button visibility 
    1254         self.editRepoButton.setVisible(self.groupByRepo) 
    1255         self.delRepoButton.setVisible(self.groupByRepo) 
     998        self.lst.clear() 
    1256999         
    12571000        # Add repositories and add-ons 
    1258         shownAddOns = set([]) 
    1259         if self.groupByRepo: 
    1260             for repo in self.repositories: 
    1261                 item = self.addRepositoryToTree(repo) 
    1262                 shownAddOns = shownAddOns.union(set(repo.addons).intersection(repo.search_index(self.searchStr))) 
    1263         else: 
    1264             addOns = {} 
    1265             for repo in self.repositories: 
    1266                 for addOnId in repo.addons: 
    1267                     if addOnId in repo.search_index(self.searchStr): 
    1268                         if addOnId in addOns: 
    1269                             addOns[addOnId].extend(repo.addons[addOnId]) 
    1270                         else: 
    1271                             addOns[addOnId] = list(repo.addons[addOnId]) 
    1272             self.addAddOnsToTree(self.tree, addOns) 
    1273             shownAddOns = set(addOns) 
    1274          
    1275         # Add add-ons that are not present in any repository 
    1276         if self.searchStr.strip() == "":   # but we do not need to search among installed add-ons 
    1277             import Orange.utils.addons 
    1278             onlyInstalledAddOns = {} 
    1279             for addOn in Orange.utils.addons.installed_addons.values(): 
    1280                 if addOn.id not in shownAddOns: 
    1281                     onlyInstalledAddOns[addOn.id] = [addOn] 
    1282             self.addAddOnsToTree(self.tree, onlyInstalledAddOns, insertToBeginning=True) 
    1283              
    1284         # Registered Add-ons 
    1285         if Orange.utils.addons.registered_addons != [] or any([id.startswith("registered:") for id in self.addOnsToAdd]): 
    1286             regiItem = QTreeWidgetItem(self.tree) 
    1287             regiItem.repository = None 
    1288             regiItem.addOnItemsDict = {} 
    1289             regiItem.setText(0, "Registered Add-ons") 
    1290             boldFont = QFont(regiItem.font(0)) 
    1291             boldFont.setWeight(QFont.Bold) 
    1292             regiItem.setFont(0, boldFont) 
    1293             self.tree.repoItems["Registered Add-ons"] = regiItem 
    1294              
    1295             addOnsToAdd = [] 
    1296             import re 
    1297             words = [word for word in re.split(Orange.utils.addons.index_re, self.searchStr.lower()) if word!=""] 
    1298             visibleAddOns = [ao for ao in Orange.utils.addons.registered_addons+[ao for ao in self.addOnsToAdd.values() if ao.id.startswith("registered:")] if all([word in ao.name for word in words])] 
    1299             self.addAddOnsToTree(regiItem, visibleAddOns) 
    1300             if selectedRegisteredAddOnId: 
    1301                 regiItem.setExpanded(True) 
    1302                 self.tree.setCurrentItem(regiItem.addOnItemsDict[selectedRegisteredAddOnId]) 
    1303              
    1304         # Restore repository items expanded state 
    1305         if len(expandedRepos)==0: 
    1306             self.tree.expandItem(self.tree.topLevelItem(0)) 
    1307         else: 
    1308             for (repo, item) in self.tree.repoItems.items(): 
    1309                 if repo in expandedRepos: 
    1310                     item.setExpanded(True) 
    1311                      
    1312         # Restore item selection 
    1313         if not selectedRegisteredAddOnId: 
    1314             select = self.tree.topLevelItem(0) 
    1315             search = None 
    1316             if selectedRepository in self.tree.repoItems: 
    1317                 select = self.tree.repoItems[selectedRepository] 
    1318                 search = select 
    1319             elif not selectedRepository: 
    1320                 search = self.tree 
    1321             if selectedAddOnId and search: 
    1322                 if selectedAddOnId in search.addOnItemsDict: 
    1323                     select = search.addOnItemsDict[selectedAddOnId] 
    1324             self.tree.setCurrentItem(select) 
    1325              
     1001        addons = {} 
     1002        for name in Orange.utils.addons.search_index(self.searchStr): 
     1003            addons[name] = Orange.utils.addons.addons[name] 
     1004        self.addAddOnsToTree(addons, selected = selected_addon, to_install=to_install, to_remove=to_remove) 
     1005        self.refreshInfoPane() 
     1006 
     1007        #TODO Should we somehow show the legacy registered addons? 
     1008 
    13261009    def searchCallback(self): 
    13271010        self.refreshView() 
Note: See TracChangeset for help on using the changeset viewer.