Ignore:
Files:
6 added
91 deleted
47 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/orngCanvas.pyw

    r10903 r11024  
    846846        t = time.time() 
    847847        lastRefresh = self.settings["lastAddonsRefresh"] 
    848         if t - lastRefresh > 7*24*3600: 
    849             if QMessageBox.question(self, "Refresh", 
    850                                     "List of add-ons in repositories has %s. Do you want to %s the lists now?" % 
    851                                     (("not yet been loaded" if lastRefresh==0 else "not been refreshed for more than a week"), 
    852                                      ("download" if lastRefresh==0 else "reload")), 
     848        dlg = orngDlgs.AddOnManagerDialog(self, self) 
     849        if t - lastRefresh > 7*24*3600 or Orange.utils.addons.addons_corrupted: 
     850            dlg.show() 
     851            if Orange.utils.addons.addons_corrupted or \ 
     852               QMessageBox.question(self, "Refresh", 
     853                                    "List of add-ons in repository has not been refreshed for more than a week. Do you want to download the list now?", 
    853854                                     QMessageBox.Yes | QMessageBox.Default, 
    854855                                     QMessageBox.No | QMessageBox.Escape) == QMessageBox.Yes: 
    855                  
    856                 anyFailed = False 
    857                 anyDone = False 
    858                 for r in Orange.utils.addons.available_repositories: 
    859                     #TODO: # Should show some progress (and enable cancellation) 
    860                     try: 
    861                         if r.refreshdata(force=True): 
    862                             anyDone = True 
    863                         else: 
    864                             anyFailed = True 
    865                     except Exception, e: 
    866                         anyFailed = True 
    867                         print "Unable to refresh repository %s! Error: %s" % (r.name, e) 
    868                  
    869                 if anyDone: 
    870                     self.settings["lastAddonsRefresh"] = t 
    871                 if anyFailed: 
    872                     QMessageBox.warning(self,'Download Failed', "Download of add-on list has failed for at least one repostitory.") 
    873          
    874         dlg = orngDlgs.AddOnManagerDialog(self, self) 
    875         if dlg.exec_() == QDialog.Accepted: 
    876             for (id, addOn) in dlg.addOnsToRemove.items(): 
     856 
    877857                try: 
    878                     addOn.uninstall(refresh=False) 
    879                     if id in dlg.addOnsToAdd.items(): 
    880                         Orange.utils.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False) 
    881                         del dlg.addOnsToAdd[id] 
     858                    dlg.reloadRepo() 
     859                    self.settings["lastAddonsRefresh"] = time.time() 
    882860                except Exception, e: 
    883                     print "Problem %s add-on %s: %s" % ("upgrading" if id in dlg.addOnsToAdd else "removing", addOn.name, e) 
    884             for (id, addOn) in dlg.addOnsToAdd.items(): 
    885                 if id.startswith("registered:"): 
    886                     try: 
    887                         Orange.utils.addons.register_addon(addOn.name, addOn.directory, refresh=False, systemwide=False) 
    888                     except Exception, e: 
    889                         print "Problem registering add-on %s: %s" % (addOn.name, e) 
    890                 else: 
    891                     try: 
    892                         Orange.utils.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False) 
    893                     except Exception, e: 
    894                         print "Problem installing add-on %s: %s" % (addOn.name, e) 
    895             if len(dlg.addOnsToAdd)+len(dlg.addOnsToRemove)>0: 
    896                 Orange.utils.addons.refresh_addons(reload_path=True) 
    897                  
     861                    import traceback 
     862                    traceback.print_exc() 
     863                    QMessageBox.warning(self,'Download Failed', "Download of add-on list has failed.") 
     864 
     865        dlg.exec_() 
     866 
    898867    def menuItemShowStatusBar(self): 
    899868        state = self.showStatusBarAction.isChecked() 
  • Orange/OrangeCanvas/orngDlgs.py

    r10778 r11037  
    77from orngCanvasItems import MyCanvasText 
    88import OWGUI, sys, os 
     9 
     10has_pip = True 
     11try: 
     12    import pip.req 
     13except ImportError: 
     14    has_pip = False 
    915 
    1016# this class is needed by signalDialog to show widgets and lines 
     
    675681 
    676682class AddOnManagerSummary(QDialog): 
    677     def __init__(self, add, remove, *args): 
     683    def __init__(self, add, remove, upgrade, *args): 
    678684        apply(QDialog.__init__,(self,) + args) 
    679685        self.setWindowTitle("Pending Actions") 
     
    697703                        lambda docSize: self.updateMinSize(docSize)) 
    698704        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() 
     705        for ao in add: 
     706            actions.append("Install %s." % ao) 
     707        for ao in remove: 
     708            actions.append("Remove %s." % ao) 
     709        for ao in upgrade: 
     710            actions.append("Upgrade %s." % ao) 
    713711        memo.setText("\n".join(actions)) 
    714712         
     
    726724        self.memo.setMinimumHeight(min(300, documentSize.height() + 2 * self.memo.frameWidth())) 
    727725 
    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          
    760726class AddOnManagerDialog(QDialog): 
    761727    def __init__(self, canvasDlg, *args): 
     
    773739         
    774740        self.groupByRepo = True 
    775         self.sortInstalledFirst = True 
    776         self.sortSingleLast = True 
    777741        self.searchStr = "" 
     742        self.to_upgrade = set() 
    778743 
    779744        searchBox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) 
    780745 
    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  
    786746        self.eSearch = self.lineEditSearch(searchBox, self, "searchStr", None, 0, tooltip = "Type in to filter (search) add-ons.", callbackOnType=True, callback=self.searchCallback) 
    787747         
    788748        # Repository & Add-on tree 
    789749         
    790         repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) 
     750        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)) 
    791751        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 = {} 
     752        self.lst = lst = QListWidget(repos) 
     753        lst.setMinimumWidth(200) 
     754        lst.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) 
     755        repos.layout().addWidget(lst) 
     756        QObject.connect(lst, SIGNAL("itemChanged(QListWidgetItem *)"), self.cbToggled) 
     757        QObject.connect(lst, SIGNAL("currentItemChanged(QListWidgetItem *, QListWidgetItem *)"), self.currentItemChanged) 
     758 
    802759        import Orange.utils.addons 
    803         self.repositories = [repo.clone() for repo in Orange.utils.addons.available_repositories] 
    804          
     760 
    805761        # Bottom info pane 
    806762         
    807         self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) 
     763        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)) 
    808764        infoPane.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    809765 
     
    833789        pInfoBtns = OWGUI.widgetBox(infoPane, orientation="horizontal", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)) 
    834790        self.webButton = OWGUI.button(pInfoBtns, self, "Open webpage", callback = self.openWebPage) 
     791        self.docButton = OWGUI.button(pInfoBtns, self, "Open documentation", callback = self.openDocsPage) 
    835792        self.listWidgetsButton = OWGUI.button(pInfoBtns, self, "List widgets", callback = self.listWidgets) 
    836793        pInfoBtns.layout().addStretch(1) 
     
    845802        lblDescription.setLineWrapMode(QTextEdit.WidgetWidth) 
    846803        lblDescription.setWordWrapMode(QTextOption.WordWrap) 
    847         QObject.connect(lblDescription.document().documentLayout(), 
    848                         SIGNAL("documentSizeChanged(const QSizeF &)"), 
    849                         lambda docSize: self.updateDescMinSize(docSize)) 
    850  
    851         # Bottom info pane for registered add-ons 
    852         self.regiInfoPane = regiInfoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) 
    853         regiInfoPane.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    854         self.lblRegisteredAddOnInfo = OWGUI.label(regiInfoPane, self, "-") 
    855         self.lblRegisteredAddOnInfo.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) 
    856804 
    857805        # Right panel 
    858806         
    859         rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)) 
     807        self.rightPanel = rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)) 
    860808        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) 
     809        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh list", callback = self.reloadRepo) 
    865810        rightPanel.layout().addSpacing(15) 
    866811        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) 
    869812        rightPanel.layout().addStretch(1) 
    870813        for btn in rightPanel.children(): 
     
    872815                btn.setMinimumHeight(btn.height()) 
    873816         
    874         # Close button 
    875         hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) 
    876         hbox.layout().addStretch(1) 
     817        # Buttons 
     818        self.hbox = hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) 
     819        busyBox = OWGUI.widgetBox(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))  # A humble stretch. 
     820        self.busyLbl = OWGUI.label(busyBox, self, "") 
     821        self.progress = QProgressBar(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) 
     822        hbox.layout().addWidget(self.progress) 
     823        self.progress.setVisible(False) 
    877824        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept) 
    878825        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject) 
     
    887834     
    888835    def accept(self): 
     836        self.to_upgrade.difference_update(self.to_remove()) 
    889837        import Orange.utils.addons 
    890         if len(self.addOnsToAdd) + len(self.addOnsToRemove) > 0: 
    891             summary = AddOnManagerSummary(self.addOnsToAdd, self.addOnsToRemove, self) 
     838        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade 
     839        if len(add) + len(remove) + len(upgrade) > 0: 
     840            summary = AddOnManagerSummary(add, remove, upgrade, self) 
    892841            if summary.exec_() == QDialog.Rejected: 
    893842                return 
    894         Orange.utils.addons.available_repositories = self.repositories 
    895         Orange.utils.addons.save_repositories() 
     843 
     844        self.busy(True) 
     845        self.repaint() 
     846        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade 
     847        for name in upgrade: 
     848            try: 
     849                self.busy("Upgrading %s ..." % name) 
     850                self.repaint() 
     851                Orange.utils.addons.upgrade(name, self.pcb) 
     852            except Exception, e: 
     853                QMessageBox.critical(self, "Error", "Problem upgrading add-on %s: %s" % (name, e)) 
     854        for name in remove: 
     855            try: 
     856                self.busy("Uninstalling %s ..." % name) 
     857                self.repaint() 
     858                Orange.utils.addons.uninstall(name, self.pcb) 
     859            except Exception, e: 
     860                QMessageBox.critical(self, "Error", "Problem uninstalling add-on %s: %s" % (name, e)) 
     861        for name in add: 
     862            try: 
     863                self.busy("Installing %s ..." % name) 
     864                self.repaint() 
     865                Orange.utils.addons.install(name, self.pcb) 
     866            except Exception, e: 
     867                QMessageBox.critical(self, "Error", "Problem installing add-on %s: %s" % (name, e)) 
     868 
     869        if len(upgrade) > 0: 
     870            QMessageBox.warning(self, "Restart Orange", "After upgrading add-ons, it is very important to restart Orange to make sure the changes have been applied.") 
     871        elif len(remove) > 0:  # Don't bother with this if there has already been one (more important) warning. 
     872            QMessageBox.warning(self, "Restart Orange", "After removal of add-ons, it is suggested that you restart Orange for the changes to become effective.") 
     873 
    896874        QDialog.accept(self) 
    897          
    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! 
     875 
     876    def busy(self, b=True): 
     877        self.progress.setMaximum(1) 
     878        self.progress.setValue(0) 
     879        self.progress.setVisible(bool(b)) 
     880        self.busyLbl.setText(b if isinstance(b, str) else "") 
     881        self.eSearch.setEnabled(not b) 
     882        self.lst.setEnabled(not b) 
     883        self.okButton.setEnabled(not b) 
     884        self.cancelButton.setEnabled(not b) 
     885        self.rightPanel.setEnabled(not b) 
     886        self.infoPane.setEnabled(not b) 
     887 
     888    def pcb(self, max, val): 
     889        self.progress.setMaximum(max) 
     890        self.progress.setValue(val) 
     891        self.progress.repaint() 
     892 
     893    def reloadRepo(self): 
     894        # Reload add-on list. 
    936895        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] 
     896        try: 
     897            self.busy("Reloading add-on repository ...") 
     898            self.repaint() 
     899            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb) 
     900        except Exception, e: 
     901            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e) 
     902        finally: 
     903            self.busy(False) 
    967904        # Finally, refresh the tree on GUI. 
    968905        self.refreshView() 
     
    970907    def upgradeCandidates(self): 
    971908        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) 
     909        import Orange.utils.addons 
     910        for ao in Orange.utils.addons.addons.values(): 
     911            if ao.installed_version and ao.available_version and ao.installed_version != ao.available_version: 
     912                result.append(ao.name) 
    986913        return result 
    987914     
     
    990917            self.upgrade(candidate, refresh=False) 
    991918        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 
     919 
     920    def upgrade(self, name=None, refresh=True): 
     921        if not name: 
     922            name = self.getAddOnIdFromItem(self.lst.currentItem()) 
     923        self.to_upgrade.add(name) 
    1002924        if refresh: 
    1003925            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) 
    1019926 
    1020927    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          
     928        addon = self.getAddOnFromItem(self.lst.currentItem()) 
     929        if addon and addon.homepage: 
     930            import webbrowser 
     931            webbrowser.open(addon.homepage) 
     932 
     933    def openDocsPage(self): 
     934        addon = self.getAddOnFromItem(self.lst.currentItem()) 
     935        if addon and addon.docs_url: 
     936            import webbrowser 
     937            webbrowser.open(addon.docs_url) 
     938 
    1027939    def listWidgets(self): 
    1028         addOn = self.getAddOnFromItem(self.tree.currentItem()) 
     940        addOn = self.getAddOnFromItem(self.lst.currentItem()) 
    1029941        if not addOn: return 
    1030942        import Orange.utils.addons 
     
    1034946         
    1035947         
    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] 
     948    def donotUpgrade(self): 
     949        id = self.getAddOnIdFromItem(self.lst.currentItem()) 
     950        self.to_upgrade.remove(id) 
    1043951        self.refreshInfoPane() 
    1044          
     952 
     953    def cbToggled(self, item): 
     954        ao = self.getAddOnFromItem(item) 
     955        if ao and not has_pip and ao.installed_version and item.checkState()==Qt.Unchecked: 
     956            QMessageBox.warning(self, "Unable to uninstall", "Pip is not installed on your system. Without it, automated removal of add-ons is not possible.\n\nInstall pip (try 'easy_install --user pip') and restart Orange to make this action possible.") 
     957            item.setCheckState(Qt.Checked) 
     958        self.refreshInfoPane(item) 
     959 
    1045960    def lineEditSearch(self, *args, **props): 
    1046961        return OWGUI.lineEdit(*args, **props) 
    1047962 
    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 
     963    def getAddOnFromItem(self, item): 
     964        return getattr(item, "addon", None) 
    1099965 
    1100966    def getAddOnIdFromItem(self, item): 
    1101         addOn = self.getAddOnFromItem(item)         
    1102         return addOn.id if addOn else None 
     967        addon = self.getAddOnFromItem(item) 
     968        return addon.name if addon else None 
    1103969         
    1104970    def refreshInfoPane(self, item=None): 
    1105971        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) 
     972            item = self.lst.currentItem() 
     973        addon = None 
     974        if item: 
     975            import Orange.utils.addons 
     976            import orngEnviron 
     977            addon = self.getAddOnFromItem(item) 
     978        if addon: 
     979            self.lblDescription.setText(addon.summary.strip() +"\n"+ addon.description.strip()) 
     980            self.lblVerAvailValue.setText(addon.available_version) 
     981 
     982            self.lblVerInstalledValue.setText(addon.installed_version if addon.installed_version else "-") #TODO Tell whether it's a system-wide installation 
     983            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 
     984            self.donotUpgradeButton.setVisible(addon.name in self.to_upgrade) 
     985            self.webButton.setVisible(bool(addon.homepage)) 
     986            self.docButton.setVisible(bool(addon.docs_url)) 
     987            self.listWidgetsButton.setVisible(False) #TODO A list of widgets is not available 
     988 
     989            if not addon.installed_version and item.checkState()==Qt.Checked: 
     990                self.lblStatus.setText("marked for installation") 
     991            elif addon.installed_version and item.checkState()!=Qt.Checked: 
     992                self.lblStatus.setText("marked for removal") 
     993            elif addon.name in self.to_upgrade: 
     994                self.lblStatus.setText("marked for upgrade") 
    1133995            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) 
     996                self.lblStatus.setText("") 
     997 
     998            self.infoPane.setVisible(True) 
    1137999        else: 
    11381000            self.infoPane.setVisible(False) 
    1139             self.regiInfoPane.setVisible(False) 
    1140          
     1001        self.enableDisableButtons() 
     1002 
    11411003    def enableDisableButtons(self): 
    1142         repo = self.getRepoFromItem(self.tree.currentItem()) 
    11431004        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() != []) 
     1005        aos = Orange.utils.addons.addons.values() 
     1006        self.upgradeAllButton.setEnabled(any(ao.installed_version and ao.available_version and 
     1007                                             ao.installed_version != ao.available_version and 
     1008                                             ao.name not in self.to_upgrade for ao in aos)) 
    11471009         
    11481010    def currentItemChanged(self, new, previous): 
    1149         # Enable/disable buttons 
    1150         self.enableDisableButtons() 
    1151              
    1152         # Refresh info pane 
     1011        # Refresh info pane & button states 
    11531012        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) ) 
     1013 
     1014    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]): 
    11801015        # 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              
     1016        addons = sorted(list(addon_dict.items()), 
     1017                        key = lambda (name, ao): name) 
     1018 
     1019        for (i, (name, ao)) in enumerate(addons): 
     1020            item = QListWidgetItem() 
     1021            self.lst.addItem(item) 
     1022            item.setText(ao.name) 
     1023            item.setCheckState(Qt.Checked if ao.installed_version and not name in to_remove or name in to_install else Qt.Unchecked) 
     1024            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled) 
     1025            item.addon = ao 
     1026            if name == selected: 
     1027                self.lst.setCurrentItem(item) 
     1028 
     1029            item.disableToggleSignal = False 
     1030 
     1031    def lst_items(self): 
     1032        for i in xrange(self.lst.count()): 
     1033            yield self.lst.item(i) 
     1034 
     1035    def to_install(self): 
     1036        return set([item.addon.name for item in self.lst_items() 
     1037                    if item.checkState()==Qt.Checked and not item.addon.installed_version]) 
     1038 
     1039    def to_remove(self): 
     1040        return set([item.addon.name for item in self.lst_items() 
     1041                    if item.checkState()!=Qt.Checked and item.addon.installed_version]) 
    12351042 
    12361043    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) 
     1044        import Orange 
    12421045        # Save current item selection 
    1243         selectedRepository = self.getRepoFromItem(self.tree.currentItem()) 
    1244         selectedAddOnId = self.getAddOnIdFromItem(self.tree.currentItem()) 
     1046        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem()) 
     1047        to_install = self.to_install() 
     1048        to_remove = self.to_remove() 
    12451049        #TODO: Save the next repository selection too, in case the current one was deleted 
    12461050 
    12471051        # 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) 
     1052        self.lst.clear() 
    12561053         
    12571054        # 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              
     1055        addons = {} 
     1056        for name in Orange.utils.addons.search_index(self.searchStr): 
     1057            addons[name.lower()] = Orange.utils.addons.addons[name.lower()] 
     1058        self.addAddOnsToTree(addons, selected = selected_addon, to_install=to_install, to_remove=to_remove) 
     1059        self.refreshInfoPane() 
     1060 
     1061        #TODO Should we somehow show the legacy registered addons? 
     1062 
    13261063    def searchCallback(self): 
    13271064        self.refreshView() 
  • Orange/OrangeCanvas/orngRegistry.py

    r10879 r11018  
    2828            return os.path.join(orngEnviron.orangeDocDir, "widgets", subDir) 
    2929        else:  # An add-on widget 
    30             addOnDocDir = self.addOn.directory_documentation() 
    31             return os.path.join(addOnDocDir, "widgets") 
     30            return None  # new style add-ons only have on-line documentation 
     31            #addOnDocDir = self.addOn.directory_documentation() 
     32            #return os.path.join(addOnDocDir, "widgets") 
    3233 
    3334 
     
    3738            self.update(widgets) 
    3839        self.name = name 
    39     
     40 
     41def load_new_addons(directories = []): 
     42    # New-type add-ons 
     43    for entry_point in pkg_resources.iter_entry_points(WIDGETS_ENTRY_POINT): 
     44        try: 
     45            module = entry_point.load() 
     46            if hasattr(module, '__path__'): 
     47                # It is a package 
     48                directories.append((entry_point.name, module.__path__[0], entry_point.name, False, module)) 
     49            else: 
     50                # It is a module 
     51                # TODO: Implement loading of widget modules 
     52                # (This should be default way to load widgets, not parsing them as files, or traversing directories, just modules and packages (which load modules)) 
     53                pass 
     54        except ImportError, err: 
     55            print "While loading, importing widgets '%s' failed: %s" % (entry_point.name, err) 
     56        except pkg_resources.DistributionNotFound, err: 
     57            print "Loading add-on '%s' failed because of a missing dependency: '%s'" % (entry_point.name, err) 
     58        except Exception, err: 
     59            print "An exception occurred during the loading of '%s':\n%r" %(entry_point.name, err) 
     60    return directories 
     61 
    4062def readCategories(silent=False): 
    4163    try: 
     
    7799             
    78100    # read list of add-ons 
    79     for addOn in Orange.utils.addons.installed_addons.values() + Orange.utils.addons.registered_addons: 
    80         addOnWidgetsDir = os.path.join(addOn.directory, "widgets") 
    81         if os.path.isdir(addOnWidgetsDir): 
    82             directories.append((addOn.name, addOnWidgetsDir, addOn, False, None)) 
    83         addOnWidgetsPrototypesDir = os.path.join(addOnWidgetsDir, "prototypes") 
    84         if os.path.isdir(addOnWidgetsPrototypesDir): 
    85             directories.append((None, addOnWidgetsPrototypesDir, addOn, True, None)) 
    86  
    87     # New-type add-ons 
    88     for entry_point in pkg_resources.iter_entry_points(WIDGETS_ENTRY_POINT): 
    89         try: 
    90             module = entry_point.load() 
    91             if hasattr(module, '__path__'): 
    92                 # It is a package 
    93                 addOn = addons.OrangeAddOn() 
    94                 addOn.name = entry_point.name 
    95                 addOn.directory = module.__path__[0] # This is invalid and useless as documentation is not there, but to set it to something 
    96                 directories.append((entry_point.name, module.__path__[0], addOn, False, module)) 
    97             else: 
    98                 # It is a module 
    99                 # TODO: Implement loading of widget modules 
    100                 # (This should be default way to load widgets, not parsing them as files, or traversing directories, just modules and packages (which load modules)) 
    101                 pass 
    102         except ImportError, err: 
    103             print "While loading, importing widgets '%s' failed: %s" % (entry_point.name, err) 
    104         except pkg_resources.DistributionNotFound, err: 
    105             print "Loading add-on '%s' failed because of a missing dependency: '%s'" % (entry_point.name, err) 
    106         except Exception, err: 
    107             print "An exception occurred during the loading of '%s':\n%r" %(entry_point.name, err) 
     101    #TODO Load registered add-ons! 
     102 
     103    load_new_addons(directories) 
    108104 
    109105    categories = {}      
     
    272268                    formatedOutList += " &nbsp; &nbsp; - " + signal.name + " (" + signal.type + ")<br>" 
    273269 
    274             addOnName = "" if not widgetInfo.addOn else " (from add-on %s)" % widgetInfo.addOn.name 
     270            addOnName = "" if not widgetInfo.addOn else " (from add-on %s)" % widgetInfo.addOn 
    275271     
    276272            widgetInfo.tooltipText = "<b><b>&nbsp;%s</b></b>%s<hr><b>Description:</b><br>&nbsp;&nbsp;%s<hr>%s<hr>%s" % (meta.name, addOnName, widgetInfo.description, formatedInList[:-4], formatedOutList[:-4])  
  • Orange/OrangeCanvas/orngSignalManager.py

    r10821 r10996  
    2020NonDefault = 16 
    2121 
    22 Explicit = 32 # Explicit - only connected if specifically requested or the only possibility  
    23  
    24 Dynamic = 64 #Dynamic output signal 
     22Explicit = 32  # Explicit - only connected if specifically requested or the only possibility 
     23 
     24Dynamic = 64  # Dynamic output signal 
    2525 
    2626 
    2727class InputSignal(object): 
    28     def __init__(self, name, signalType, handler, parameters = Single + NonDefault, oldParam = 0): 
     28    def __init__(self, name, signalType, handler, 
     29                 parameters=Single + NonDefault, 
     30                 oldParam=0): 
    2931        self.name = name 
    3032        self.type = signalType 
     
    3739            self.single = parameters 
    3840            self.default = not oldParam 
     41            self.explicit = 0 
    3942            return 
    4043 
     
    4346        if not (parameters & Default or parameters & NonDefault): 
    4447            parameters += NonDefault 
     48 
    4549        self.single = parameters & Single 
    4650        self.default = parameters & Default 
    4751        self.explicit = parameters & Explicit 
    48          
    49          
     52 
     53 
    5054class OutputSignal(object): 
    51     def __init__(self, name, signalType, parameters = Single + NonDefault): 
     55    def __init__(self, name, signalType, parameters=Single + NonDefault): 
    5256        self.name = name 
    5357        self.type = signalType 
    5458 
    55         if type(parameters) == str: parameters = eval(parameters) 
    56         if parameters in [0,1]: # old definition of parameters 
     59        if type(parameters) == str: 
     60            parameters = eval(parameters) 
     61 
     62        if parameters in [0, 1]:  # old definition of parameters 
    5763            self.default = not parameters 
     64            self.single = 1 
     65            self.explicit = 0 
     66            self.dynamic = 0 
    5867            return 
    5968 
    60         if not (parameters & Default or parameters & NonDefault): parameters += NonDefault 
     69        if not (parameters & Default or parameters & NonDefault): 
     70            parameters += NonDefault 
     71 
    6172        self.single = parameters & Single 
    6273        self.default = parameters & Default 
    6374        self.explicit = parameters & Explicit 
    64          
     75 
    6576        self.dynamic = parameters & Dynamic 
    6677        if self.dynamic and self.single: 
    6778            print "Output signal can not be Multiple and Dynamic" 
    6879            self.dynamic = 0 
    69              
    70              
     80 
     81 
    7182def canConnect(output, input, dynamic=False): 
    7283    ret = issubclass(output.type, input.type) 
  • Orange/OrangeWidgets/Classify/OWClassificationTree.py

    r11096 r11216  
    3535        self.inputs = [("Data", ExampleTable, self.setData), 
    3636                       ("Preprocess", PreprocessedLearner, self.setPreprocessor)] 
    37          
     37 
    3838        self.outputs = [("Learner", Orange.classification.tree.TreeLearner), 
    39                         ("Classification Tree", Orange.classification.tree.TreeClassifier), 
    40                         ("Classification Tree Graph", Orange.network.Graph)] 
     39                        ("Classification Tree", Orange.classification.tree.TreeClassifier), ] 
    4140 
    4241        self.name = 'Classification Tree' 
     
    146145            self.classifier = None 
    147146 
    148         tree_graph = None 
    149         if self.classifier is not None: 
    150             tree_graph = self.classifier.to_network() 
    151  
    152147        self.send("Classification Tree", self.classifier) 
    153         self.send("Classification Tree Graph", tree_graph) 
    154  
    155148 
    156149    def measureChanged(self): 
  • Orange/OrangeWidgets/Classify/OWClassificationTreeGraph.py

    r11096 r11216  
    1818         
    1919    def setR(self, r): 
     20        self.prepareGeometryChange() 
    2021        self.r = r 
    2122         
  • Orange/OrangeWidgets/Data/OWPythonScript.py

    r11096 r11216  
    266266        OWWidget.__init__(self, parent, signalManager, 'Python Script') 
    267267         
    268         self.inputs = [("in_data", ExampleTable, self.setExampleTable), ("in_distance", orange.SymMatrix, self.setDistanceMatrix), ("in_network", Orange.network.Graph, self.setNetwork), ("in_learner", orange.Learner, self.setLearner), ("in_classifier", orange.Classifier, self.setClassifier)] 
    269         self.outputs = [("out_data", ExampleTable), ("out_distance", orange.SymMatrix), ("out_network", Orange.network.Graph), ("out_learner", orange.Learner), ("out_classifier", orange.Classifier, Dynamic)] 
    270          
     268        self.inputs = [("in_data", ExampleTable, self.setExampleTable), ("in_distance", orange.SymMatrix, self.setDistanceMatrix), ("in_learner", orange.Learner, self.setLearner), ("in_classifier", orange.Classifier, self.setClassifier)] 
     269        self.outputs = [("out_data", ExampleTable), ("out_distance", orange.SymMatrix), ("out_learner", orange.Learner), ("out_classifier", orange.Classifier, Dynamic)] 
     270 
     271        try: 
     272            self.inputs.append(("in_network", Orange.network.Graph, self.setNetwork)) 
     273            self.outputs.append(("out_network", Orange.network.Graph)) 
     274        except: 
     275            pass 
     276 
    271277        self.in_data = None 
    272278        self.in_network = None 
  • Orange/OrangeWidgets/Evaluate/OWTestLearners.py

    r10983 r11000  
    262262 
    263263    def sendReport(self): 
     264        method = [("Method", self.resamplingMethods[self.resampling])] 
     265 
    264266        exset = [] 
     267 
    265268        if self.resampling == 0: 
    266269            exset = [("Folds", self.nFolds)] 
    267270        elif self.resampling == 2: 
    268             exset = [("Repetitions", self.pRepeat), ("Proportion of training instances", "%i%%" % self.pLearning)] 
     271            exset = [("Repetitions", self.pRepeat), 
     272                     ("Proportion of training instances", "%i%%" \ 
     273                      % self.pLearning)] 
    269274        else: 
    270275            exset = [] 
     276 
     277        if self.data and \ 
     278                isinstance(self.data.domain.classVar, orange.EnumVariable): 
     279            target = [("Target class", 
     280                       self.data.domain.classVar.values[self.targetClass])] 
     281        else: 
     282            target = [] 
     283 
    271284        if not self.ismultilabel(): 
    272             self.reportSettings("Validation method", 
    273                             [("Method", self.resamplingMethods[self.resampling])] 
    274                             + exset + 
    275                             ([("Target class", self.data.domain.classVar.values[self.targetClass])] if self.data else [])) 
    276         else: 
    277              self.reportSettings("Validation method", 
    278                             [("Method", self.resamplingMethods[self.resampling])] 
    279                             + exset) 
    280          
     285            self.reportSettings("Validation method", method + exset + target) 
     286        else: 
     287            self.reportSettings("Validation method", method + exset) 
     288 
    281289        self.reportData(self.data) 
    282290 
    283         if self.data:         
     291        if self.data: 
    284292            self.reportSection("Results") 
    285293            learners = [(l.time, l) for l in self.learners.values()] 
     
    287295            learners = [lt[1] for lt in learners] 
    288296            usestat = self.get_usestat() 
    289             res = "<table><tr><th></th>"+"".join("<th><b>%s</b></th>" % hr for hr in [s.label for i, s in enumerate(self.stat) if i in usestat])+"</tr>" 
    290             for i, l in enumerate(learners): 
    291                 res += "<tr><th><b>%s</b></th>" % l.name 
    292                 if l.scores: 
     297            # table header 
     298            res = "<table><tr><th></th>" + \ 
     299                  "".join("<th><b>%s</b></th>" % s.label \ 
     300                          for i, s in enumerate(self.stat) \ 
     301                          if i in usestat) + \ 
     302                  "</tr>" 
     303 
     304            for i, learner in enumerate(learners): 
     305                res += "<tr><th><b>%s</b></th>" % learner.name 
     306                if learner.scores: 
    293307                    for j in usestat: 
    294                         scr = l.scores[j] 
    295                         res += "<td>" + ("%.4f" % scr if scr is not None else "") + "</td>" 
     308                        score = learner.scores[j] 
     309                        score = "%.4f" % score if score is not None else "" 
     310                        res += "<td>" + score + "</td>" 
    296311                res += "</tr>" 
    297312            res += "</table>" 
    298313            self.reportRaw(res) 
    299              
     314 
    300315    def score(self, ids): 
    301316        """compute scores for the list of learners""" 
     
    311326        n = len(self.data.domain.attributes)*2 
    312327        indices = orange.MakeRandomIndices2(p0=min(n, len(self.data)), stratified=orange.MakeRandomIndices2.StratifiedIfPossible) 
    313         new = self.data.selectref(indices(self.data)) 
    314          
     328        new = self.data.selectref(indices(self.data), 0) 
     329 
    315330        multilabel = self.ismultilabel() 
    316331         
  • Orange/OrangeWidgets/OWTreeViewer2D.py

    r10743 r11038  
    6161        if self.scene(): 
    6262            self.scene().fixPos() 
    63          
     63 
     64 
     65def luminance(color): 
     66    """Return the `luminance`_ (sRGB color space) of the color. 
     67 
     68    .. _luminance: http://en.wikipedia.org/wiki/Luminance_(colorimetry) 
     69 
     70    """ 
     71    r, g, b, _ = color.getRgb() 
     72    Y = 0.2126 * r + 0.7152 * g + 0.0722 * b 
     73    return Y 
     74 
     75 
    6476class TextTreeNode(QGraphicsTextItem, graph_node): 
    65      
    66     borderRadius = pyqtProperty("int", 
    67                 lambda self: getattr(self, "_borderRadius", 0), 
    68                 lambda self, val: (setattr(self, "_borderRadius", val), self.update()) and None, 
    69                 doc="Rounded rect's border radius" 
    70                 ) 
    71     backgroundBrush = pyqtProperty("QBrush", 
    72                 lambda self: getattr(self, "_backgroundBrush", getattr(self.scene(), "defaultItemBrush", Qt.NoBrush)), 
    73                 lambda self, brush: (setattr(self, "_backgroundBrush", brush), self.update()) and None, 
    74                 doc="Background brush" 
    75                 ) 
    76     truncateText = pyqtProperty("bool", 
    77                 lambda self: getattr(self, "_truncateText", False), 
    78                 lambda self, val: (setattr(self, "_truncateText", val), self.updateContents()), 
    79                 doc="Truncate text") 
    80      
     77    """A Tree node with text. 
     78    """ 
     79    def setBorderRadius(self, r): 
     80        if self._borderRadius != r: 
     81            self.prepareGeometryChange() 
     82            self._borderRadius = r 
     83            self.update() 
     84 
     85    def borderRadius(self): 
     86        return getattr(self, "_borderRadius", 0) 
     87 
     88    borderRadius = pyqtProperty("int", fget=borderRadius, fset=setBorderRadius, 
     89                                doc="Rounded rect's border radius") 
     90 
     91    def setBackgroundBrush(self, brush): 
     92        """Set node's background brush. 
     93        """ 
     94        if self._backgroundBrush != brush: 
     95            self._backgroundBrush = QBrush(brush) 
     96            color = brush.color() 
     97            if luminance(color) > 30: 
     98                self.setDefaultTextColor(Qt.black) 
     99            else: 
     100                self.setDefaultTextColor(Qt.white) 
     101            self.update() 
     102 
     103    def backgroundBrush(self): 
     104        """Return the node's background brush. 
     105        """ 
     106        brush = getattr(self, "_backgroundBrush", 
     107                        getattr(self.scene(), "defaultItemBrush", Qt.NoBrush)) 
     108        return QBrush(brush) 
     109 
     110    backgroundBrush = pyqtProperty("QBrush", fget=backgroundBrush, 
     111                                   fset=setBackgroundBrush, 
     112                                   doc="Background brush") 
     113 
     114    def setTruncateText(self, truncate): 
     115        """Set the truncateText to truncate. If true the text will 
     116        be truncated to fit inside the node's box, otherwise it will 
     117        overflow. 
     118 
     119        """ 
     120        if self._truncateText != truncate: 
     121            self._truncateText = truncate 
     122            self.updateContents() 
     123 
     124    def truncateText(self): 
     125        return getattr(self, "_truncateText", False) 
     126 
     127    truncateText = pyqtProperty("bool", fget=truncateText, 
     128                                fset=setTruncateText, 
     129                                doc="Truncate text") 
     130 
    81131    def __init__(self, tree, parent, *args, **kwargs): 
    82132        QGraphicsTextItem.__init__(self, *args) 
    83133        graph_node.__init__(self, **kwargs) 
     134        self._borderRadius = 0 
     135        self._backgroundBrush = None 
     136        self._truncateText = False 
     137 
    84138        self.tree = tree 
    85139        self.parent = parent 
     
    88142        self.setFont(font) 
    89143        self.droplet = GraphicsDroplet(-5, 0, 10, 10, self, self.scene()) 
    90          
     144 
    91145        self.droplet.setPos(self.rect().center().x(), self.rect().height()) 
    92          
    93         self.connect(self.document(), SIGNAL("contentsChanged()"), self.updateContents) 
     146 
     147        self.connect(self.document(), SIGNAL("contentsChanged()"), 
     148                     self.updateContents) 
    94149        self.isOpen = True 
    95150        self.setFlag(QGraphicsItem.ItemIsSelectable, True) 
    96          
     151 
    97152    def setHtml(self, html): 
    98153        if qVersion() < "4.5": 
  • Orange/OrangeWidgets/Prototypes/OWInteractionGraphProto.py

    r10466 r11012  
    1717from orngCI import FeatureByCartesianProduct 
    1818import OWGUI 
    19 from orangeom import Network 
    2019 
    2120class IntGraphView(QGraphicsView): 
     
    4342 
    4443        self.inputs = [("Data", ExampleTable, self.setData)] 
    45         self.outputs = [("Data", ExampleTable), ("Interacting Features", list), ("Selected Features", list), ("Network", Network)] 
     44        self.outputs = [("Data", ExampleTable), ("Interacting Features", list), ("Selected Features", list)] #, ("Network", Network)] 
    4645 
    4746 
     
    328327 
    329328        self.send("Data", data) 
    330         self.send("Network", self.graph) 
     329        #self.send("Network", self.graph) 
    331330 
    332331    ######################################### 
  • Orange/__init__.py

    r10974 r11031  
    77ADDONS_ENTRY_POINT = 'orange.addons' 
    88 
    9 from . import orange 
     9import os, sys 
    1010 
    11 # Definitely ugly, but I see no other workaround. 
    12 # When, e.g. data.io executes "from orange import ExampleTable" 
    13 # orange gets imported again since it is not in sys.modules 
    14 # before this entire file is executed 
    15 import sys 
    16 sys.modules["orange"] = orange 
     11if not os.environ.get('READTHEDOCS', None): 
     12    from . import orange 
     13    # Definitely ugly, but I see no other workaround. 
     14    # When, e.g. data.io executes "from orange import ExampleTable" 
     15    # orange gets imported again since it is not in sys.modules 
     16    # before this entire file is executed 
     17    sys.modules["orange"] = orange 
    1718 
    1819# Little trick so that legacy imports work automatically 
     
    8687_import("feature.scoring") 
    8788_import("feature.selection") 
    88  
    89 _import("network") 
    9089 
    9190_import("stat") 
  • Orange/doc/extend-widgets/OWLearningCurveA.py

    r9671 r10997  
    1515        OWWidget.__init__(self, parent, signalManager, 'LearningCurveA') 
    1616 
    17         self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)] 
     17        self.inputs = [("Data", ExampleTable, self.dataset), 
     18                       ("Learner", orange.Learner, self.learner, Multiple)] 
    1819         
    1920        self.folds = 5     # cross validation folds 
  • Orange/doc/extend-widgets/OWLearningCurveB.py

    r9671 r10997  
    1515        OWWidget.__init__(self, parent, signalManager, 'LearningCurveA') 
    1616 
    17         self.inputs = [("Train Data", ExampleTable, self.trainset), ("Test Data", ExampleTable, self.testset, 1, 1), ("Learner", orange.Learner, self.learner, 0)] 
     17        self.inputs = [("Train Data", ExampleTable, self.trainset, Default), 
     18                       ("Test Data", ExampleTable, self.testset), 
     19                       ("Learner", orange.Learner, self.learner, Multiple)] 
    1820         
    1921        self.folds = 5     # cross validation folds 
  • Orange/doc/extend-widgets/OWLearningCurveC.py

    r9671 r10997  
    2020        OWWidget.__init__(self, parent, signalManager, 'LearningCurveC') 
    2121 
    22         self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)] 
     22        self.inputs = [("Data", ExampleTable, self.dataset), 
     23                       ("Learner", orange.Learner, self.learner, Multiple)] 
    2324 
    2425        self.folds = 5     # cross validation folds 
  • Orange/doc/extend-widgets/OWLearningCurve_plot.py

    r9671 r10997  
    2020        OWWidget.__init__(self, parent, signalManager, 'LearningCurveC') 
    2121 
    22         self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)] 
     22        self.inputs = [("Data", ExampleTable, self.dataset), 
     23                       ("Learner", orange.Learner, self.learner, Multiple)] 
    2324 
    2425        self.folds = 5     # cross validation folds 
  • Orange/doc/extend-widgets/api.htm

    r9671 r10997  
    9191input channel in the following way:</p> 
    9292 
    93 <xmp class="code">self.inputs = [("Learners", orange.Learner, self.learner, 0)] 
     93<xmp class="code">self.inputs = [("Learners", orange.Learner, self.learner, Multiple)] 
    9494</xmp> 
    9595 
    96 <p>where the last argument refers if we have a "single token channel" 
    97 (last argument should be 1, default) or a "multitoken" one (0 as a 
    98 fourth element of the list holding channel definition). For the above 
    99 declared channel, the receiving function should include an extra 
    100 argument for the ID, like:</p> 
     96<p>where the last argument refers if we have a "Single" (default if not 
     97specified) or a "Multiple" channel. For the above declared channel, the 
     98receiving function should include an extra argument for the ID, like:</p> 
    10199 
    102100<xmp class="code">def learner(self, learnertoken, tokenid): 
     
    110108would declare input channels like:</p> 
    111109 
    112 <xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata), ("Additional Data", orange.ExampleTable, self.otherdata)] 
     110<xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata), 
     111               ("Additional Data", orange.ExampleTable, self.otherdata)] 
    113112</xmp> 
    114113 
    115114<p>and we connect this widget in Orange Canvas to a sending widget 
    116115that has a single orange.ExampleTable output channel, Canvas would 
    117 bring up Set Channels dialog. There, a sending widget's channel would 
     116bring up Set Channels dialog. There, a sending widget's channel could 
    118117be connected to both receiving channels. As we would often prefer to 
    119118connect to a single (default) channel instead (still allowing user of 
    120 Orange Canvas to set up a different schema manually), we set all the 
    121 channels to which we do not want an automatic connection into a "minor 
    122 mode". We do this by the using the fifth element in the channel 
     119Orange Canvas to set up a different schema manually), we set that channel 
     120as the default. We do this by the using the fourth element in the channel 
    123121definition list, like:</p> 
    124122 
    125 <xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata), 
    126 ("Additional Data", orange.ExampleTable, self.otherdata, 1, 1)] 
     123<xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata, Default), 
     124               ("Additional Data", orange.ExampleTable, self.otherdata)] 
    127125</xmp> 
    128126 
  • Orange/doc/extend-widgets/channels.htm

    r9671 r10997  
    5353widget are defined by:</p> 
    5454 
    55 <xmp class="code">self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)] 
     55<xmp class="code">self.inputs = [("Data", ExampleTable, self.dataset), 
     56               ("Learner", orange.Learner, self.learner, Multiple)] 
    5657</xmp> 
    5758 
    5859<p>Notice that everything is pretty much the same as it was with 
    59 widgets from previous lessons, the only difference being "0" as the 
    60 last value in the list that defines the <code>Learner</code> 
    61 channel. This "0" says that this is a multi-input channel. If it would 
    62 be "1" (default), it would mean that the widget can receive the input 
    63 only from one widget.</p> 
     60widgets from previous lessons, the only difference being 
     61<code>Multiple + Default</code> as the last value in the list that defines 
     62the <code>Learner</code> channel. This <code>Multiple + Default</code> says 
     63that this is a multi-input channel and is the default input for its type. 
     64If it would be unspecified then by default value of 
     65<code>Single + NonDefault</code> would be used. That would mean that the 
     66widget can receive the input only from one widget and is not the default input 
     67channel for its type (more on default channels later).</p> 
    6468 
    6569<p>How does the widget know from which widget did the token come from? 
     
    206210<img src="datasampler-totable.png"> 
    207211 
    208 <p>we would get a following window quering users for information on 
     212<p>we would get a following window querying users for information on 
    209213which channels to connect:</p> 
    210214 
     
    224228 
    225229<p>When enlisting the input channel of the same type, the non-default 
    226 channels have a special fourth value in the channel specification 
    227 list. So for our new <a href="OWLearningCurveB.py">learning curve</a> 
    228 widget, the channel specification is:</p> 
    229  
    230 <xmp class="code">self.inputs = [("Train Data", ExampleTable, self.trainset), ("Test Data", ExampleTable, self.testset, 1, 1), ("Learner", orange.Learner, self.learner, 0)] 
    231 </xmp> 
    232  
    233 <p>That is, the <code>Test Data</code> channel is a single-token 
    234 channel (third parameter) which is not a default one (where "1" 
    235 indicates that this is a so-called minor channel). To test how this 
    236 works, connect a file widget to a learning curve widget and - nothing 
    237 will really happen:</p> 
     230channels have a special flag in the channel specification list. So for 
     231our new <a href="OWLearningCurveB.py">learning curve</a> widget, the 
     232channel specification is:</p> 
     233 
     234<xmp class="code">self.inputs = [("Train Data", ExampleTable, self.trainset, Default), 
     235               ("Test Data", ExampleTable, self.testset), 
     236               ("Learner", orange.Learner, self.learner, Multiple)] 
     237</xmp> 
     238 
     239<p>That is, the <code>Train Data</code> channel is a single-token 
     240channel which is a default one (third parameter). Note that the flags can 
     241be added (or OR-d) together so <code>Default + Multi</code> is a valid flag. 
     242To test how this works, connect a file widget to a learning curve widget and 
     243- nothing will really happen:</p> 
    238244 
    239245<img src="file-to-learningcurveb.png"> 
  • Orange/doc/reference/pathfinder.py

    r9671 r11012  
    1 import orngNetwork 
     1import Orange 
    22from orangeom import Pathfinder 
    33from pylab import * 
     
    2222         
    2323# Read a demo network from a file 
    24 net = orngNetwork.Network.read('demo.net') 
     24net = Orange.core.Network.read('demo.net') 
    2525 
    2626# Compute a layout for plotting 
    27 netOp = orngNetwork.NetworkOptimization(net) 
     27netOp = Orange.core.NetworkOptimization(net) 
    2828netOp.fruchtermanReingold(100, 1000) 
    2929 
  • Orange/doc/reference/pf_progress.py

    r9671 r11012  
    1 import orngNetwork 
     1import Orange 
    22from orangeom import Pathfinder 
    33 
     
    99 
    1010# Read a demo network from a file 
    11 net = orngNetwork.Network.read('demo.net') 
     11net = Orange.core.Network.read('demo.net') 
    1212 
    1313# Choose some parameters 
  • Orange/evaluation/testing.py

    r10985 r11039  
    581581 
    582582            test_results = self.test_with_indices(learners, examples, indices, 
    583                                                   preprocessors=list(preprocessors) + [("L", select_proportion_preprocessor)], 
    584                                                   callback=callback) 
     583                preprocessors=[("L", select_proportion_preprocessor)] + 
     584                list(preprocessors), callback=callback) 
    585585            all_results.append(test_results) 
    586586        return all_results 
  • Orange/fixes/fix_changed_names.py

    r10714 r11004  
    186186           "orngBayes.BayesClassifier":"Orange.classification.bayes.NaiveClassifier", 
    187187           "orngBayes.printModel": "Orange.classification.bayes.printModel", 
    188  
    189            "orngNetwork.MdsTypeClass":"Orange.network.MdsTypeClass", 
    190            "orngNetwork.Network":"Orange.network.Network", 
    191            "orngNetwork.NetworkOptimization":"Orange.network.NetworkOptimization", 
    192            "orngNetwork.NetworkClustering":"Orange.network.NetworkClustering", 
    193            "orange.Graph":"Orange.network.Graph", 
    194            "orange.GraphAsList":"Orange.network.GraphAsList", 
    195            "orange.GraphAsMatrix":"Orange.network.GraphAsMatrix", 
    196            "orange.GraphAsTree":"Orange.network.GraphAsTree", 
    197188 
    198189           "orange.TreeLearner": "Orange.classification.tree.TreeLearner", 
  • Orange/fixes/fix_orange_imports.py

    r10718 r11006  
    4343           "orngBayes":"Orange.classification.bayes", 
    4444           "orngLR":"Orange.classification.logreg", 
    45            "orngNetwork":"Orange.network", 
    4645           "orngMisc":"Orange.misc", 
    4746           "orngEnsemble":"Orange.ensemble", 
  • Orange/orng/orngInteract.py

    r9671 r11012  
    3636import orngContingency, numpy 
    3737import warnings, math, string, copy 
    38 import orngNetwork 
     38import Orange 
    3939 
    4040def _nicefloat(f,sig): 
     
    313313        ### NODE DRAWING ### 
    314314        map = {} 
    315         graph = orngNetwork.Network(len(atts), 0) 
     315        graph = Orange.core.Network(len(atts), 0) 
    316316        table = [] 
    317317         
  • Orange/regression/tree.py

    r10395 r11028  
    2929================= 
    3030 
    31 .. include:: /SimpleTreeLearner.txt 
     31.. include:: SimpleTreeLearner.txt 
    3232 
    3333A basic example of using a SimpleTreeLearner is shown below: 
  • Orange/testing/regression/results_tests_20/modules_som1.py.txt

    r10464 r11044  
    11node: 0 0 
     2    [6.3, 3.3, 6.0, 2.5, 'Iris-virginica'] 
     3node: 0 1 
     4node: 0 2 
     5    [6.7, 3.1, 5.6, 2.4, 'Iris-virginica'] 
     6    [6.7, 3.3, 5.7, 2.5, 'Iris-virginica'] 
     7node: 0 3 
     8node: 0 4 
     9node: 0 5 
     10    [7.7, 3.8, 6.7, 2.2, 'Iris-virginica'] 
     11    [7.9, 3.8, 6.4, 2.0, 'Iris-virginica'] 
     12node: 0 6 
     13node: 0 7 
     14    [7.7, 2.6, 6.9, 2.3, 'Iris-virginica'] 
     15    [7.7, 2.8, 6.7, 2.0, 'Iris-virginica'] 
     16node: 0 8 
     17    [7.6, 3.0, 6.6, 2.1, 'Iris-virginica'] 
     18node: 0 9 
     19    [7.2, 3.2, 6.0, 1.8, 'Iris-virginica'] 
     20node: 0 10 
     21node: 0 11 
     22    [7.0, 3.2, 4.7, 1.4, 'Iris-versicolor'] 
     23    [6.9, 3.1, 4.9, 1.5, 'Iris-versicolor'] 
     24node: 0 12 
     25node: 0 13 
     26    [6.7, 3.1, 4.4, 1.4, 'Iris-versicolor'] 
     27node: 0 14 
     28    [6.4, 3.2, 4.5, 1.5, 'Iris-versicolor'] 
     29node: 0 15 
     30node: 0 16 
     31    [5.5, 4.2, 1.4, 0.2, 'Iris-setosa'] 
     32node: 0 17 
    233    [5.8, 4.0, 1.2, 0.2, 'Iris-setosa'] 
    334    [5.7, 4.4, 1.5, 0.4, 'Iris-setosa'] 
    4 node: 0 1 
    5 node: 0 2 
     35node: 0 18 
     36node: 0 19 
    637    [5.7, 3.8, 1.7, 0.3, 'Iris-setosa'] 
    7 node: 0 3 
    8 node: 0 4 
    9     [5.4, 3.4, 1.7, 0.2, 'Iris-setosa'] 
    10 node: 0 5 
    11 node: 0 6 
    12     [4.9, 2.5, 4.5, 1.7, 'Iris-virginica'] 
    13 node: 0 7 
    14 node: 0 8 
    15     [5.7, 2.8, 4.5, 1.3, 'Iris-versicolor'] 
    16 node: 0 9 
    17 node: 0 10 
    18     [5.4, 3.0, 4.5, 1.5, 'Iris-versicolor'] 
    19 node: 0 11 
    20 node: 0 12 
    21     [6.0, 3.4, 4.5, 1.6, 'Iris-versicolor'] 
    22 node: 0 13 
    23     [5.9, 3.2, 4.8, 1.8, 'Iris-versicolor'] 
    24 node: 0 14 
    25     [5.9, 3.0, 5.1, 1.8, 'Iris-virginica'] 
    26 node: 0 15 
    27     [5.8, 2.7, 5.1, 1.9, 'Iris-virginica'] 
    28     [5.8, 2.7, 5.1, 1.9, 'Iris-virginica'] 
    29 node: 0 16 
    30 node: 0 17 
    31     [5.8, 2.8, 5.1, 2.4, 'Iris-virginica'] 
    32 node: 0 18 
    33     [6.2, 3.4, 5.4, 2.3, 'Iris-virginica'] 
    34 node: 0 19 
     38node: 1 0 
    3539    [6.3, 3.4, 5.6, 2.4, 'Iris-virginica'] 
    36 node: 1 0 
    37     [5.4, 3.9, 1.3, 0.4, 'Iris-setosa'] 
    38     [5.5, 4.2, 1.4, 0.2, 'Iris-setosa'] 
    3940node: 1 1 
    40     [5.4, 3.9, 1.7, 0.4, 'Iris-setosa'] 
    4141node: 1 2 
    4242node: 1 3 
    4343node: 1 4 
    44     [5.4, 3.4, 1.5, 0.4, 'Iris-setosa'] 
     44    [7.2, 3.6, 6.1, 2.5, 'Iris-virginica'] 
    4545node: 1 5 
    4646node: 1 6 
    4747node: 1 7 
    48     [5.5, 2.6, 4.4, 1.2, 'Iris-versicolor'] 
    4948node: 1 8 
     49    [7.3, 2.9, 6.3, 1.8, 'Iris-virginica'] 
    5050node: 1 9 
    51     [5.6, 3.0, 4.5, 1.5, 'Iris-versicolor'] 
     51    [7.2, 3.0, 5.8, 1.6, 'Iris-virginica'] 
    5252node: 1 10 
     53    [6.7, 3.0, 5.0, 1.7, 'Iris-versicolor'] 
    5354node: 1 11 
    54     [6.1, 3.0, 4.6, 1.4, 'Iris-versicolor'] 
     55    [6.7, 3.1, 4.7, 1.5, 'Iris-versicolor'] 
    5556node: 1 12 
     57    [6.6, 3.0, 4.4, 1.4, 'Iris-versicolor'] 
    5658node: 1 13 
    57     [6.1, 3.0, 4.9, 1.8, 'Iris-virginica'] 
    58     [6.0, 3.0, 4.8, 1.8, 'Iris-virginica'] 
    5959node: 1 14 
    6060node: 1 15 
    61     [5.7, 2.5, 5.0, 2.0, 'Iris-virginica'] 
     61    [5.4, 3.9, 1.7, 0.4, 'Iris-setosa'] 
     62    [5.2, 4.1, 1.5, 0.1, 'Iris-setosa'] 
    6263node: 1 16 
    6364node: 1 17 
     65    [5.4, 3.9, 1.3, 0.4, 'Iris-setosa'] 
    6466node: 1 18 
     67    [5.4, 3.7, 1.5, 0.2, 'Iris-setosa'] 
    6568node: 1 19 
    66     [6.3, 3.3, 6.0, 2.5, 'Iris-virginica'] 
    6769node: 2 0 
    68     [5.2, 4.1, 1.5, 0.1, 'Iris-setosa'] 
     70    [6.2, 3.4, 5.4, 2.3, 'Iris-virginica'] 
    6971node: 2 1 
     72    [6.4, 3.2, 5.3, 2.3, 'Iris-virginica'] 
    7073node: 2 2 
    71     [5.1, 3.8, 1.9, 0.4, 'Iris-setosa'] 
    7274node: 2 3 
    73     [5.4, 3.7, 1.5, 0.2, 'Iris-setosa'] 
    74     [5.3, 3.7, 1.5, 0.2, 'Iris-setosa'] 
    7575node: 2 4 
    76     [5.5, 3.5, 1.3, 0.2, 'Iris-setosa'] 
     76    [6.9, 3.2, 5.7, 2.3, 'Iris-virginica'] 
    7777node: 2 5 
     78    [6.8, 3.2, 5.9, 2.3, 'Iris-virginica'] 
    7879node: 2 6 
    79     [5.2, 2.7, 3.9, 1.4, 'Iris-versicolor'] 
     80    [7.1, 3.0, 5.9, 2.1, 'Iris-virginica'] 
    8081node: 2 7 
     82    [7.7, 3.0, 6.1, 2.3, 'Iris-virginica'] 
    8183node: 2 8 
    82     [5.6, 2.7, 4.2, 1.3, 'Iris-versicolor'] 
     84    [7.4, 2.8, 6.1, 1.9, 'Iris-virginica'] 
    8385node: 2 9 
    8486node: 2 10 
    85     [6.0, 2.9, 4.5, 1.5, 'Iris-versicolor'] 
    8687node: 2 11 
     88    [6.8, 2.8, 4.8, 1.4, 'Iris-versicolor'] 
    8789node: 2 12 
    88     [6.1, 2.9, 4.7, 1.4, 'Iris-versicolor'] 
    89     [6.1, 2.8, 4.7, 1.2, 'Iris-versicolor'] 
     90    [6.6, 2.9, 4.6, 1.3, 'Iris-versicolor'] 
    9091node: 2 13 
     92    [6.4, 2.9, 4.3, 1.3, 'Iris-versicolor'] 
    9193node: 2 14 
    92     [6.2, 2.8, 4.8, 1.8, 'Iris-virginica'] 
     94    [6.1, 2.8, 4.0, 1.3, 'Iris-versicolor'] 
    9395node: 2 15 
    9496node: 2 16 
    95     [5.6, 2.8, 4.9, 2.0, 'Iris-virginica'] 
     97    [5.1, 3.8, 1.9, 0.4, 'Iris-setosa'] 
     98    [5.1, 3.8, 1.6, 0.2, 'Iris-setosa'] 
    9699node: 2 17 
     100    [5.1, 3.8, 1.5, 0.3, 'Iris-setosa'] 
    97101node: 2 18 
     102    [5.3, 3.7, 1.5, 0.2, 'Iris-setosa'] 
     103node: 2 19 
     104    [5.5, 3.5, 1.3, 0.2, 'Iris-setosa'] 
     105node: 3 0 
     106node: 3 1 
     107    [6.7, 3.0, 5.2, 2.3, 'Iris-virginica'] 
     108node: 3 2 
     109    [6.9, 3.1, 5.1, 2.3, 'Iris-virginica'] 
     110node: 3 3 
     111    [6.9, 3.1, 5.4, 2.1, 'Iris-virginica'] 
     112node: 3 4 
     113    [6.7, 3.3, 5.7, 2.1, 'Iris-virginica'] 
     114node: 3 5 
     115    [6.5, 3.0, 5.8, 2.2, 'Iris-virginica'] 
     116node: 3 6 
     117node: 3 7 
     118node: 3 8 
     119    [6.7, 2.5, 5.8, 1.8, 'Iris-virginica'] 
     120node: 3 9 
     121node: 3 10 
     122    [6.3, 2.5, 4.9, 1.5, 'Iris-versicolor'] 
     123node: 3 11 
     124    [6.5, 2.8, 4.6, 1.5, 'Iris-versicolor'] 
     125node: 3 12 
     126    [6.2, 2.9, 4.3, 1.3, 'Iris-versicolor'] 
     127node: 3 13 
     128node: 3 14 
     129    [5.6, 2.9, 3.6, 1.3, 'Iris-versicolor'] 
     130node: 3 15 
     131node: 3 16 
     132    [5.1, 3.7, 1.5, 0.4, 'Iris-setosa'] 
     133node: 3 17 
     134    [5.0, 3.6, 1.4, 0.2, 'Iris-setosa'] 
     135node: 3 18 
     136    [5.2, 3.5, 1.5, 0.2, 'Iris-setosa'] 
     137node: 3 19 
     138    [5.4, 3.4, 1.7, 0.2, 'Iris-setosa'] 
     139    [5.4, 3.4, 1.5, 0.4, 'Iris-setosa'] 
     140node: 4 0 
     141    [5.8, 2.8, 5.1, 2.4, 'Iris-virginica'] 
     142node: 4 1 
     143node: 4 2 
     144node: 4 3 
     145node: 4 4 
     146    [6.8, 3.0, 5.5, 2.1, 'Iris-virginica'] 
     147node: 4 5 
     148node: 4 6 
    98149    [6.4, 2.8, 5.6, 2.1, 'Iris-virginica'] 
    99150    [6.4, 2.8, 5.6, 2.2, 'Iris-virginica'] 
    100 node: 2 19 
    101     [6.5, 3.0, 5.8, 2.2, 'Iris-virginica'] 
    102 node: 3 0 
    103     [5.1, 3.8, 1.5, 0.3, 'Iris-setosa'] 
    104     [5.1, 3.8, 1.6, 0.2, 'Iris-setosa'] 
    105 node: 3 1 
    106 node: 3 2 
    107     [5.2, 3.5, 1.5, 0.2, 'Iris-setosa'] 
    108     [5.2, 3.4, 1.4, 0.2, 'Iris-setosa'] 
    109 node: 3 3 
    110 node: 3 4 
    111     [5.1, 3.3, 1.7, 0.5, 'Iris-setosa'] 
    112     [5.0, 3.5, 1.6, 0.6, 'Iris-setosa'] 
    113 node: 3 5 
    114 node: 3 6 
    115     [5.5, 2.5, 4.0, 1.3, 'Iris-versicolor'] 
    116 node: 3 7 
    117     [5.7, 2.8, 4.1, 1.3, 'Iris-versicolor'] 
    118 node: 3 8 
    119     [5.6, 3.0, 4.1, 1.3, 'Iris-versicolor'] 
    120     [5.7, 3.0, 4.2, 1.2, 'Iris-versicolor'] 
    121     [5.7, 2.9, 4.2, 1.3, 'Iris-versicolor'] 
    122 node: 3 9 
    123     [5.9, 3.0, 4.2, 1.5, 'Iris-versicolor'] 
    124 node: 3 10 
    125     [6.2, 2.9, 4.3, 1.3, 'Iris-versicolor'] 
    126 node: 3 11 
    127     [6.7, 3.1, 4.4, 1.4, 'Iris-versicolor'] 
    128 node: 3 12 
    129     [6.4, 3.2, 4.5, 1.5, 'Iris-versicolor'] 
    130 node: 3 13 
    131     [6.3, 3.3, 4.7, 1.6, 'Iris-versicolor'] 
    132 node: 3 14 
    133 node: 3 15 
    134     [6.5, 3.2, 5.1, 2.0, 'Iris-virginica'] 
    135     [6.5, 3.0, 5.2, 2.0, 'Iris-virginica'] 
    136 node: 3 16 
    137     [6.4, 3.2, 5.3, 2.3, 'Iris-virginica'] 
    138 node: 3 17 
    139 node: 3 18 
    140     [6.7, 3.1, 5.6, 2.4, 'Iris-virginica'] 
    141 node: 3 19 
    142     [6.7, 3.3, 5.7, 2.5, 'Iris-virginica'] 
    143 node: 4 0 
    144     [5.1, 3.7, 1.5, 0.4, 'Iris-setosa'] 
    145 node: 4 1 
    146     [5.0, 3.6, 1.4, 0.2, 'Iris-setosa'] 
    147 node: 4 2 
    148     [5.1, 3.5, 1.4, 0.2, 'Iris-setosa'] 
    149 node: 4 3 
    150     [5.1, 3.4, 1.5, 0.2, 'Iris-setosa'] 
    151 node: 4 4 
    152     [5.0, 3.4, 1.6, 0.4, 'Iris-setosa'] 
    153 node: 4 5 
    154 node: 4 6 
    155     [5.5, 2.3, 4.0, 1.3, 'Iris-versicolor'] 
    156151node: 4 7 
    157152node: 4 8 
     
    160155node: 4 11 
    161156node: 4 12 
    162     [6.6, 3.0, 4.4, 1.4, 'Iris-versicolor'] 
    163157node: 4 13 
     158    [5.8, 2.7, 4.1, 1.0, 'Iris-versicolor'] 
     159    [5.8, 2.6, 4.0, 1.2, 'Iris-versicolor'] 
    164160node: 4 14 
    165     [6.7, 3.1, 4.7, 1.5, 'Iris-versicolor'] 
     161    [5.8, 2.7, 3.9, 1.2, 'Iris-versicolor'] 
    166162node: 4 15 
    167     [6.7, 3.0, 5.0, 1.7, 'Iris-versicolor'] 
    168163node: 4 16 
     164    [5.0, 3.5, 1.6, 0.6, 'Iris-setosa'] 
    169165node: 4 17 
    170     [6.7, 3.0, 5.2, 2.3, 'Iris-virginica'] 
    171166node: 4 18 
     167    [5.1, 3.5, 1.4, 0.2, 'Iris-setosa'] 
     168    [5.1, 3.5, 1.4, 0.3, 'Iris-setosa'] 
    172169node: 4 19 
     170    [5.2, 3.4, 1.4, 0.2, 'Iris-setosa'] 
     171    [5.1, 3.4, 1.5, 0.2, 'Iris-setosa'] 
    173172node: 5 0 
     173    [5.6, 2.8, 4.9, 2.0, 'Iris-virginica'] 
    174174node: 5 1 
    175     [5.1, 3.5, 1.4, 0.3, 'Iris-setosa'] 
    176     [5.0, 3.5, 1.3, 0.3, 'Iris-setosa'] 
    177175node: 5 2 
     176    [6.5, 3.2, 5.1, 2.0, 'Iris-virginica'] 
     177    [6.5, 3.0, 5.2, 2.0, 'Iris-virginica'] 
    178178node: 5 3 
    179     [5.0, 3.4, 1.5, 0.2, 'Iris-setosa'] 
    180179node: 5 4 
    181     [4.8, 3.4, 1.6, 0.2, 'Iris-setosa'] 
    182180node: 5 5 
    183181node: 5 6 
     182    [6.4, 2.7, 5.3, 1.9, 'Iris-virginica'] 
    184183node: 5 7 
    185     [5.6, 2.9, 3.6, 1.3, 'Iris-versicolor'] 
    186184node: 5 8 
    187     [5.8, 2.7, 3.9, 1.2, 'Iris-versicolor'] 
     185    [6.1, 2.6, 5.6, 1.4, 'Iris-virginica'] 
    188186node: 5 9 
    189     [6.1, 2.8, 4.0, 1.3, 'Iris-versicolor'] 
     187    [6.0, 2.2, 5.0, 1.5, 'Iris-virginica'] 
    190188node: 5 10 
    191     [6.4, 2.9, 4.3, 1.3, 'Iris-versicolor'] 
     189    [6.2, 2.2, 4.5, 1.5, 'Iris-versicolor'] 
    192190node: 5 11 
    193     [6.5, 2.8, 4.6, 1.5, 'Iris-versicolor'] 
     191    [6.3, 2.3, 4.4, 1.3, 'Iris-versicolor'] 
    194192node: 5 12 
    195     [6.6, 2.9, 4.6, 1.3, 'Iris-versicolor'] 
     193    [6.0, 2.2, 4.0, 1.0, 'Iris-versicolor'] 
    196194node: 5 13 
    197     [6.8, 2.8, 4.8, 1.4, 'Iris-versicolor'] 
     195    [5.6, 2.5, 3.9, 1.1, 'Iris-versicolor'] 
    198196node: 5 14 
    199     [7.0, 3.2, 4.7, 1.4, 'Iris-versicolor'] 
    200     [6.9, 3.1, 4.9, 1.5, 'Iris-versicolor'] 
     197    [5.7, 2.6, 3.5, 1.0, 'Iris-versicolor'] 
    201198node: 5 15 
    202199node: 5 16 
    203     [6.9, 3.1, 5.1, 2.3, 'Iris-virginica'] 
     200    [5.1, 3.3, 1.7, 0.5, 'Iris-setosa'] 
     201    [5.0, 3.4, 1.6, 0.4, 'Iris-setosa'] 
    204202node: 5 17 
    205     [6.8, 3.0, 5.5, 2.1, 'Iris-virginica'] 
    206     [6.9, 3.1, 5.4, 2.1, 'Iris-virginica'] 
     203    [5.0, 3.4, 1.5, 0.2, 'Iris-setosa'] 
    207204node: 5 18 
    208     [6.9, 3.2, 5.7, 2.3, 'Iris-virginica'] 
     205    [5.0, 3.5, 1.3, 0.3, 'Iris-setosa'] 
    209206node: 5 19 
    210     [6.7, 3.3, 5.7, 2.1, 'Iris-virginica'] 
    211     [6.8, 3.2, 5.9, 2.3, 'Iris-virginica'] 
    212 node: 6 0 
    213     [4.6, 3.6, 1.0, 0.2, 'Iris-setosa'] 
    214 node: 6 1 
    215     [4.6, 3.4, 1.4, 0.3, 'Iris-setosa'] 
    216 node: 6 2 
    217 node: 6 3 
    218207    [5.0, 3.2, 1.2, 0.2, 'Iris-setosa'] 
    219208    [5.0, 3.3, 1.4, 0.2, 'Iris-setosa'] 
     209node: 6 0 
     210    [5.7, 2.5, 5.0, 2.0, 'Iris-virginica'] 
     211node: 6 1 
     212node: 6 2 
     213    [5.9, 3.0, 5.1, 1.8, 'Iris-virginica'] 
     214node: 6 3 
    220215node: 6 4 
     216    [6.3, 2.9, 5.6, 1.8, 'Iris-virginica'] 
     217    [6.5, 3.0, 5.5, 1.8, 'Iris-virginica'] 
     218    [6.4, 3.1, 5.5, 1.8, 'Iris-virginica'] 
    221219node: 6 5 
    222220node: 6 6 
    223221node: 6 7 
    224     [5.7, 2.6, 3.5, 1.0, 'Iris-versicolor'] 
    225     [5.5, 2.4, 3.7, 1.0, 'Iris-versicolor'] 
     222    [6.3, 2.8, 5.1, 1.5, 'Iris-virginica'] 
    226223node: 6 8 
    227     [5.6, 2.5, 3.9, 1.1, 'Iris-versicolor'] 
     224    [6.0, 2.7, 5.1, 1.6, 'Iris-versicolor'] 
    228225node: 6 9 
    229     [5.8, 2.7, 4.1, 1.0, 'Iris-versicolor'] 
    230     [5.8, 2.6, 4.0, 1.2, 'Iris-versicolor'] 
    231226node: 6 10 
    232227node: 6 11 
     228    [5.5, 2.6, 4.4, 1.2, 'Iris-versicolor'] 
    233229node: 6 12 
    234230node: 6 13 
    235     [6.0, 2.7, 5.1, 1.6, 'Iris-versicolor'] 
     231    [5.5, 2.3, 4.0, 1.3, 'Iris-versicolor'] 
    236232node: 6 14 
    237     [6.3, 2.8, 5.1, 1.5, 'Iris-virginica'] 
     233    [5.5, 2.4, 3.8, 1.1, 'Iris-versicolor'] 
     234    [5.5, 2.4, 3.7, 1.0, 'Iris-versicolor'] 
    238235node: 6 15 
    239236node: 6 16 
     237    [4.8, 3.4, 1.9, 0.2, 'Iris-setosa'] 
    240238node: 6 17 
    241     [7.2, 3.2, 6.0, 1.8, 'Iris-virginica'] 
     239    [4.8, 3.4, 1.6, 0.2, 'Iris-setosa'] 
    242240node: 6 18 
    243     [7.1, 3.0, 5.9, 2.1, 'Iris-virginica'] 
    244241node: 6 19 
    245242node: 7 0 
    246     [4.4, 3.2, 1.3, 0.2, 'Iris-setosa'] 
     243    [5.8, 2.7, 5.1, 1.9, 'Iris-virginica'] 
     244    [5.8, 2.7, 5.1, 1.9, 'Iris-virginica'] 
    247245node: 7 1 
    248     [4.6, 3.1, 1.5, 0.2, 'Iris-setosa'] 
    249     [4.6, 3.2, 1.4, 0.2, 'Iris-setosa'] 
    250246node: 7 2 
    251     [4.7, 3.2, 1.3, 0.2, 'Iris-setosa'] 
    252247node: 7 3 
    253248node: 7 4 
    254     [4.8, 3.4, 1.9, 0.2, 'Iris-setosa'] 
    255     [4.7, 3.2, 1.6, 0.2, 'Iris-setosa'] 
    256249node: 7 5 
     250    [6.0, 3.4, 4.5, 1.6, 'Iris-versicolor'] 
    257251node: 7 6 
    258252node: 7 7 
    259     [5.5, 2.4, 3.8, 1.1, 'Iris-versicolor'] 
    260253node: 7 8 
    261254node: 7 9 
     255    [5.6, 3.0, 4.5, 1.5, 'Iris-versicolor'] 
     256    [5.4, 3.0, 4.5, 1.5, 'Iris-versicolor'] 
    262257node: 7 10 
    263     [6.0, 2.2, 5.0, 1.5, 'Iris-virginica'] 
     258    [5.7, 2.8, 4.5, 1.3, 'Iris-versicolor'] 
    264259node: 7 11 
    265     [6.3, 2.5, 4.9, 1.5, 'Iris-versicolor'] 
     260    [5.6, 2.7, 4.2, 1.3, 'Iris-versicolor'] 
    266261node: 7 12 
     262    [5.5, 2.5, 4.0, 1.3, 'Iris-versicolor'] 
    267263node: 7 13 
    268264node: 7 14 
    269265node: 7 15 
    270266node: 7 16 
    271     [7.2, 3.0, 5.8, 1.6, 'Iris-virginica'] 
     267    [4.8, 3.1, 1.6, 0.2, 'Iris-setosa'] 
    272268node: 7 17 
    273     [7.4, 2.8, 6.1, 1.9, 'Iris-virginica'] 
     269    [4.7, 3.2, 1.6, 0.2, 'Iris-setosa'] 
    274270node: 7 18 
    275     [7.7, 3.0, 6.1, 2.3, 'Iris-virginica'] 
     271    [4.7, 3.2, 1.3, 0.2, 'Iris-setosa'] 
     272    [4.6, 3.4, 1.4, 0.3, 'Iris-setosa'] 
    276273node: 7 19 
    277     [7.2, 3.6, 6.1, 2.5, 'Iris-virginica'] 
     274    [4.6, 3.6, 1.0, 0.2, 'Iris-setosa'] 
    278275node: 8 0 
    279     [4.3, 3.0, 1.1, 0.1, 'Iris-setosa'] 
    280276node: 8 1 
     277node: 8 2 
     278node: 8 3 
     279    [6.1, 3.0, 4.9, 1.8, 'Iris-virginica'] 
     280node: 8 4 
     281    [5.9, 3.2, 4.8, 1.8, 'Iris-versicolor'] 
     282node: 8 5 
     283node: 8 6 
     284node: 8 7 
     285    [6.1, 2.9, 4.7, 1.4, 'Iris-versicolor'] 
     286    [6.1, 2.8, 4.7, 1.2, 'Iris-versicolor'] 
     287node: 8 8 
     288    [6.0, 2.9, 4.5, 1.5, 'Iris-versicolor'] 
     289node: 8 9 
     290node: 8 10 
     291node: 8 11 
     292    [5.7, 2.8, 4.1, 1.3, 'Iris-versicolor'] 
     293node: 8 12 
     294node: 8 13 
     295    [5.2, 2.7, 3.9, 1.4, 'Iris-versicolor'] 
     296node: 8 14 
     297node: 8 15 
     298node: 8 16 
     299    [5.0, 3.0, 1.6, 0.2, 'Iris-setosa'] 
     300node: 8 17 
     301node: 8 18 
     302    [4.6, 3.1, 1.5, 0.2, 'Iris-setosa'] 
     303    [4.6, 3.2, 1.4, 0.2, 'Iris-setosa'] 
     304node: 8 19 
     305    [4.4, 3.2, 1.3, 0.2, 'Iris-setosa'] 
     306node: 9 0 
     307    [6.3, 2.5, 5.0, 1.9, 'Iris-virginica'] 
     308node: 9 1 
     309    [6.3, 2.7, 4.9, 1.8, 'Iris-virginica'] 
     310node: 9 2 
     311    [6.2, 2.8, 4.8, 1.8, 'Iris-virginica'] 
     312node: 9 3 
     313    [6.0, 3.0, 4.8, 1.8, 'Iris-virginica'] 
     314node: 9 4 
     315node: 9 5 
     316    [6.3, 3.3, 4.7, 1.6, 'Iris-versicolor'] 
     317node: 9 6 
     318    [6.1, 3.0, 4.6, 1.4, 'Iris-versicolor'] 
     319node: 9 7 
     320node: 9 8 
     321    [5.9, 3.0, 4.2, 1.5, 'Iris-versicolor'] 
     322node: 9 9 
     323node: 9 10 
     324    [5.6, 3.0, 4.1, 1.3, 'Iris-versicolor'] 
     325    [5.7, 3.0, 4.2, 1.2, 'Iris-versicolor'] 
     326    [5.7, 2.9, 4.2, 1.3, 'Iris-versicolor'] 
     327node: 9 11 
     328node: 9 12 
     329    [4.9, 2.5, 4.5, 1.7, 'Iris-virginica'] 
     330node: 9 13 
     331node: 9 14 
     332    [4.9, 2.4, 3.3, 1.0, 'Iris-versicolor'] 
     333    [5.0, 2.0, 3.5, 1.0, 'Iris-versicolor'] 
     334    [5.0, 2.3, 3.3, 1.0, 'Iris-versicolor'] 
     335    [5.1, 2.5, 3.0, 1.1, 'Iris-versicolor'] 
     336node: 9 15 
     337node: 9 16 
     338    [4.9, 3.0, 1.4, 0.2, 'Iris-setosa'] 
     339    [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
     340    [4.8, 3.0, 1.4, 0.1, 'Iris-setosa'] 
     341    [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
     342    [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
     343node: 9 17 
     344    [4.8, 3.0, 1.4, 0.3, 'Iris-setosa'] 
     345node: 9 18 
    281346    [4.4, 2.9, 1.4, 0.2, 'Iris-setosa'] 
    282347    [4.4, 3.0, 1.3, 0.2, 'Iris-setosa'] 
    283 node: 8 2 
    284 node: 8 3 
    285     [4.8, 3.0, 1.4, 0.1, 'Iris-setosa'] 
    286     [4.8, 3.0, 1.4, 0.3, 'Iris-setosa'] 
    287 node: 8 4 
    288     [4.8, 3.1, 1.6, 0.2, 'Iris-setosa'] 
    289 node: 8 5 
    290 node: 8 6 
    291     [5.1, 2.5, 3.0, 1.1, 'Iris-versicolor'] 
    292 node: 8 7 
    293 node: 8 8 
    294 node: 8 9 
    295     [6.0, 2.2, 4.0, 1.0, 'Iris-versicolor'] 
    296 node: 8 10 
    297 node: 8 11 
    298 node: 8 12 
    299     [6.3, 2.7, 4.9, 1.8, 'Iris-virginica'] 
    300 node: 8 13 
    301 node: 8 14 
    302     [6.1, 2.6, 5.6, 1.4, 'Iris-virginica'] 
    303 node: 8 15 
    304 node: 8 16 
    305     [6.7, 2.5, 5.8, 1.8, 'Iris-virginica'] 
    306 node: 8 17 
    307     [7.3, 2.9, 6.3, 1.8, 'Iris-virginica'] 
    308 node: 8 18 
    309 node: 8 19 
    310 node: 9 0 
     348node: 9 19 
     349    [4.3, 3.0, 1.1, 0.1, 'Iris-setosa'] 
    311350    [4.5, 2.3, 1.3, 0.3, 'Iris-setosa'] 
    312 node: 9 1 
    313 node: 9 2 
    314 node: 9 3 
    315     [4.9, 3.0, 1.4, 0.2, 'Iris-setosa'] 
    316 node: 9 4 
    317     [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
    318     [5.0, 3.0, 1.6, 0.2, 'Iris-setosa'] 
    319     [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
    320     [4.9, 3.1, 1.5, 0.1, 'Iris-setosa'] 
    321 node: 9 5 
    322 node: 9 6 
    323     [4.9, 2.4, 3.3, 1.0, 'Iris-versicolor'] 
    324     [5.0, 2.3, 3.3, 1.0, 'Iris-versicolor'] 
    325 node: 9 7 
    326     [5.0, 2.0, 3.5, 1.0, 'Iris-versicolor'] 
    327 node: 9 8 
    328 node: 9 9 
    329 node: 9 10 
    330     [6.2, 2.2, 4.5, 1.5, 'Iris-versicolor'] 
    331     [6.3, 2.3, 4.4, 1.3, 'Iris-versicolor'] 
    332 node: 9 11 
    333 node: 9 12 
    334     [6.3, 2.5, 5.0, 1.9, 'Iris-virginica'] 
    335 node: 9 13 
    336     [6.4, 2.7, 5.3, 1.9, 'Iris-virginica'] 
    337 node: 9 14 
    338     [6.3, 2.9, 5.6, 1.8, 'Iris-virginica'] 
    339 node: 9 15 
    340     [6.5, 3.0, 5.5, 1.8, 'Iris-virginica'] 
    341     [6.4, 3.1, 5.5, 1.8, 'Iris-virginica'] 
    342 node: 9 16 
    343 node: 9 17 
    344     [7.6, 3.0, 6.6, 2.1, 'Iris-virginica'] 
    345     [7.7, 2.6, 6.9, 2.3, 'Iris-virginica'] 
    346     [7.7, 2.8, 6.7, 2.0, 'Iris-virginica'] 
    347 node: 9 18 
    348 node: 9 19 
    349     [7.7, 3.8, 6.7, 2.2, 'Iris-virginica'] 
    350     [7.9, 3.8, 6.4, 2.0, 'Iris-virginica'] 
  • Orange/testing/unit/tests/test_display_name_mapping.py

    r10970 r11011  
    1414        """ 
    1515        #modules in exempt contain source files intended for addons 
    16         exempt = ["Orange.multitarget.tree",  ] 
     16        exempt = ["Orange.multitarget.tree", 
     17                  "Orange.network", ] 
    1718 
    1819        for cls in orange.__dict__.values(): 
  • Orange/testing/unit/tests/test_linear.py

    r10773 r11017  
     1import cPickle 
     2 
    13import Orange 
    24from Orange.testing import testing 
     
    1012import numpy as np 
    1113 
    12 def multiclass_from_1_vs_rest(dec_values, class_var): 
     14 
     15def decision_values(classifier, instance): 
     16    """Return the decision values (numpy.array) for classifying `instance`. 
     17    """ 
     18    instance = Orange.data.Table(classifier.domain, [instance]) 
     19    (instance,) = instance.to_numpy_MA("A") 
     20 
     21    x = instance.filled(0.0) 
     22    if classifier.bias > 0.0: 
     23        x = np.hstack([x, [[classifier.bias]]]) 
     24 
     25    w = np.array(classifier.weights) 
     26 
     27    return np.dot(x, w.T).ravel() 
     28 
     29 
     30def classify_from_weights(classifier, instance): 
     31    """Classify the instance using classifier's weights. 
     32    """ 
     33    dec_values = decision_values(classifier, instance) 
     34 
     35    class_var = classifier.class_var 
    1336    if len(class_var.values) > 2: 
     37        # TODO: Check how liblinear handles ties 
    1438        return class_var(int(np.argmax(dec_values))) 
    1539    else: 
    1640        return class_var(0 if dec_values[0] > 0 else 1) 
    1741 
    18 def binary_classifier_test(self, data): 
     42 
     43def classify_from_weights_test(self, classifier, data): 
    1944    class_var = data.domain.class_var 
    2045    if isinstance(class_var, Orange.feature.Discrete): 
    21         cl_values = class_var.values 
    22         if self.classifier.bias >= 0: 
    23             bias = [self.classifier.bias] 
    24         else: 
    25             bias = [] 
    2646        for inst in data[:]: 
    27             dec_values = [] 
    28             inst_v = [float(v) if not v.is_special() else 0.0 \ 
    29                       for v in Orange.data.Instance(self.classifier.domain, inst)] 
    30             inst_v = inst_v[:-1] + bias 
    31             for w in self.classifier.weights: 
    32                 dec_values.append(np.dot(inst_v, w)) 
    33             pval1 = self.classifier(inst) 
    34             pval2 = multiclass_from_1_vs_rest(dec_values, class_var) 
    35             if len(cl_values) > 2: 
    36                 self.assertEqual(pval1, pval2) 
    37             else: 
    38                 #TODO: handle order switch 
    39                 pass 
     47            pval1 = classifier(inst) 
     48            pval2 = classify_from_weights(classifier, inst) 
     49            self.assertEqual(pval1, pval2, 
     50                             msg="classifier and classify_from_weights return " 
     51                                 "different values") 
     52 
    4053 
    4154@testing.test_on_data 
    4255def test_learner_on(self, dataset): 
    4356    testing.LearnerTestCase.test_learner_on(self, dataset) 
     57 
    4458    n_vals = len(dataset.domain.class_var.values) 
    4559    if n_vals > 2: 
     
    4761    else: 
    4862        self.assertEquals(len(self.classifier.weights), 1) 
     63 
    4964    n_features = len(self.classifier.domain.attributes) 
    5065    if self.classifier.bias >= 0: 
    5166        n_features += 1 
    52      
     67 
    5368    self.assertTrue(all(len(w) == n_features \ 
    5469                        for w in self.classifier.weights 
    5570                        )) 
    56      
    57     binary_classifier_test(self, dataset) 
     71 
     72    classify_from_weights_test(self, self.classifier, dataset) 
     73 
    5874 
    5975@testing.test_on_data 
    6076def test_learner_with_bias_on(self, dataset): 
    61     import cPickle 
    6277    learner = self.learner 
    6378    learner_b = cPickle.loads(cPickle.dumps(learner)) 
     
    6580    try: 
    6681        self.learner = learner_b 
     82        test_learner_on(self, dataset) 
    6783    finally: 
    6884        self.learner = learner 
    69     test_learner_on(self, dataset) 
    70           
     85 
     86 
     87def split(data, value): 
     88    pos = [inst for inst in data if inst.get_class() == value] 
     89    neg = [inst for inst in data if inst.get_class() != value] 
     90    return Orange.data.Table(pos), Orange.data.Table(neg) 
     91 
     92 
     93def missing_instances_test(self): 
     94    """Test the learner on a dataset with no instances for 
     95    some class. 
     96 
     97    """ 
     98    data = Orange.data.Table("iris") 
     99    class_var = data.domain.class_var 
     100 
     101    for i, value in enumerate(class_var.values): 
     102        _, train = split(data, value) 
     103        classifier = self.learner(train) 
     104 
     105        self.assertEqual(len(classifier.weights), len(class_var.values), 
     106                        msg="Number of weight vectors differs from the number " 
     107                            "of class values") 
     108 
     109        dec_values = [decision_values(classifier, instance) \ 
     110                      for instance in data] 
     111 
     112        self.assertTrue(all(val[i] == 0.0 for val in dec_values), 
     113                        msg="Non zero decision value for unseen class") 
     114 
     115        classify_from_weights_test(self, classifier, data) 
     116 
    71117 
    72118@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
     
    76122    test_learner_on = test_learner_on 
    77123    test_learner_with_bias_on = test_learner_with_bias_on 
     124    test_missing_instances = missing_instances_test 
     125 
    78126 
    79127@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
    80128class TestLinearSVMLearnerL2R_L2LOSS(testing.LearnerTestCase): 
    81129    LEARNER = LinearSVMLearner(sover_type=LinearSVMLearner.L2R_L2LOSS) 
    82      
     130 
    83131    test_learner_on = test_learner_on 
    84132    test_learner_with_bias_on = test_learner_with_bias_on 
     133    test_missing_instances = missing_instances_test 
     134 
    85135 
    86136@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
    87137class TestLinearSVMLearnerL2R_L1LOSS_DUAL(testing.LearnerTestCase): 
    88138    LEARNER = LinearSVMLearner(sover_type=LinearSVMLearner.L2R_L1LOSS_DUAL) 
    89      
     139 
    90140    test_learner_on = test_learner_on 
    91141    test_learner_with_bias_on = test_learner_with_bias_on 
     142    test_missing_instances = missing_instances_test 
     143 
    92144 
    93145@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
    94146class TestLinearSVMLearnerL2R_L1LOSS(testing.LearnerTestCase): 
    95147    LEARNER = LinearSVMLearner(sover_type=LinearSVMLearner.L2R_L2LOSS) 
    96          
     148 
    97149    test_learner_on = test_learner_on 
    98150    test_learner_with_bias_on = test_learner_with_bias_on 
     151    test_missing_instances = missing_instances_test 
     152 
    99153 
    100154@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
    101155class TestLinearSVMLearnerL1R_L2LOSS(testing.LearnerTestCase): 
    102156    LEARNER = LinearSVMLearner(sover_type=LinearSVMLearner.L1R_L2LOSS) 
    103      
     157 
    104158    test_learner_on = test_learner_on 
    105159    test_learner_with_bias_on = test_learner_with_bias_on 
     160    test_missing_instances = missing_instances_test 
     161 
    106162 
    107163@datasets_driven(datasets=testing.CLASSIFICATION_DATASETS) 
    108 class TestLinearSVMLearnerL1R_L2LOSS(testing.LearnerTestCase): 
     164class TestLinearSVMLearnerMCSVM_CSS(testing.LearnerTestCase): 
    109165    LEARNER = LinearSVMLearner(sover_type=LinearSVMLearner.MCSVM_CS) 
    110      
     166 
    111167    test_learner_on = test_learner_on 
    112168    test_learner_with_bias_on = test_learner_with_bias_on 
     169    test_missing_instances = missing_instances_test 
     170 
    113171 
    114172if __name__ == "__main__": 
  • Orange/utils/__init__.py

    r10981 r10998  
    625625 
    626626import urllib2 
     627import posixpath 
     628import os 
     629 
     630from contextlib import contextmanager 
     631import StringIO 
     632 
     633@contextmanager 
     634def finishing(obj): 
     635    """ Calls obj.finish() on context exit. 
     636    """ 
     637    yield obj 
     638    obj.finish() 
     639 
     640def guess_size(fileobj): 
     641    try: 
     642        if isinstance(fileobj, file): 
     643            return os.fstat(fileobj.fileno()).st_size 
     644        elif isinstance(fileobj, StringIO.StringIO): 
     645            pos = fileobj.tell() 
     646            fileobj.seek(0, 2) 
     647            length = fileobj.tell() - pos 
     648            fileobj.seek(pos, 0) 
     649            return length 
     650        elif isinstance(fileobj, urllib.addinfourl): 
     651            length = fileobj.headers.get("content-length", None) 
     652            return length 
     653    except Exception, ex: 
     654        pass 
    627655 
    628656def copyfileobj(src, dst, buffer=2**10, content_len=None, progress=None): 
  • Orange/utils/addons.py

    r10581 r11037  
     1from __future__ import absolute_import 
    12""" 
    23============================== 
     
    910soon as it is imported, the following initialization takes place: the list of 
    1011installed add-ons is loaded, their directories are added to python path 
    11 (:obj:`sys.path`) the callback list is initialized the stored repository list is 
     12(:obj:`sys.path`) the callback list is initialized, the stored repository list is 
    1213loaded. The most important consequence of importing the module is thus the 
    13 ability to import add-ons' modules, because they are now in the python path. 
    14  
    15 .. attribute:: available_repositories 
    16  
    17    List of add-on repository descriptors (instances of 
    18    :class:`OrangeAddOnRepository`). 
    19  
    20 .. attribute:: addon_directories 
    21  
    22    List of directories that have been added to the path to make use of add-ons 
    23    possible; see :obj:`add_addon_directories_to_path`. 
    24  
    25 .. attribute:: registered_addons 
    26  
    27    A list of registered add-on descriptors (instances of 
    28    :class:`OrangeRegisteredAddOn`). 
    29  
    30 .. attribute:: available_addons 
    31  
    32    A dictionary mapping URLs of repositories to instances of 
    33    :class:`OrangeAddOnRepository`. 
    34  
    35 .. attribute:: installed_addons 
    36  
    37    A dictionary mapping GUIDs to instances of :class:`OrangeAddOnInstalled`. 
    38  
    39 .. autofunction:: load_installed_addons_from_dir 
    40  
    41 .. autofunction:: repository_list_filename 
    42  
    43 .. autofunction:: load_repositories 
    44  
    45 .. autofunction:: save_repositories 
    46  
    47 .. autofunction:: update_default_repositories 
    48  
    49 .. autofunction:: add_addon_directories_to_path 
    50  
    51 .. autofunction:: install_addon 
    52  
    53 .. autofunction:: install_addon_from_repo 
    54  
    55 .. autofunction:: load_addons 
    56  
    57 .. autofunction:: refresh_addons 
    58  
    59 .. autofunction:: register_addon 
    60  
    61 .. autofunction:: unregister_addon 
    62  
    63 Add-on descriptors and packaging routines 
    64 ========================================= 
    65  
    66 .. autofunction:: suggest_version 
    67  
    68 .. autoclass:: OrangeRegisteredAddOn 
    69    :members: 
    70    :show-inheritance: 
    71  
    72 .. autoclass:: OrangeAddOn 
    73    :members: 
    74    :show-inheritance: 
    75  
    76 .. autoclass:: OrangeAddOnInRepo 
    77    :members: 
    78    :show-inheritance: 
    79  
    80 .. autoclass:: OrangeAddOnInstalled 
    81    :members: 
    82    :show-inheritance: 
    83  
    84 Add-on repository descriptors 
    85 ============================= 
    86  
    87 .. autoclass:: OrangeAddOnRepository 
    88    :members: 
    89    :show-inheritance: 
    90     
    91 .. autoclass:: OrangeDefaultAddOnRepository 
    92    :members: 
    93    :show-inheritance: 
    94  
    95 Exception classes 
    96 ================= 
    97  
    98 .. autoclass:: RepositoryException 
    99    :members: 
    100    :show-inheritance: 
    101  
    102 .. autoclass:: InstallationException 
    103    :members: 
    104    :show-inheritance: 
    105  
    106 .. autoclass:: PackingException 
    107    :members: 
    108    :show-inheritance: 
     14injection of add-ons into the namespace. 
    10915 
    11016""" 
    11117 
    112  
    113 import xml.dom.minidom 
     18#TODO Document this module. 
     19 
     20import socket 
     21import shelve 
     22import xmlrpclib 
     23import warnings 
    11424import re 
     25import pkg_resources 
     26import tempfile 
     27import tarfile 
     28import shutil 
    11529import os 
    116 import shutil 
    11730import sys 
    118 import glob 
    119 import time 
    120 import socket 
    121 import urllib  # urllib because we need 'urlretrieve' 
    122 import urllib2 # urllib2 because it reports HTTP Errors for 'urlopen' 
    123 import bisect 
    12431import platform 
     32from collections import namedtuple, defaultdict 
    12533 
    12634import Orange.utils.environ 
    127 import widgetparser 
    128 from fileutil import * 
    129 from fileutil import _zip_open 
    130 from zipfile import ZipFile 
    131  
    132 import warnings 
     35 
     36ADDONS_ENTRY_POINT="orange.addons" 
    13337 
    13438socket.setdefaulttimeout(120)  # In seconds. 
    13539 
    136 class PackingException(Exception): 
    137     """ 
    138     An exception that occurs during add-on packaging. Behaves exactly as 
    139     :class:`Exception`. 
    140      
    141     """ 
    142     pass 
    143  
    144 def suggest_version(current_version): 
    145     """ 
    146     Automatically construct a version string of form "year.month.day[.number]".  
    147     If the passed "current version" is already in this format and contains 
    148     identical date, the last number is incremented if it exists; otherwise ".1" 
    149     is appended. 
    150      
    151     :param current_version: version on which to base the new version; is used 
    152         only in case it is in the same format. 
    153     :type current_version: str 
    154      
    155     """ 
    156      
    157     version = time.strftime("%Y.%m.%d") 
    158     try: 
    159         xmlver_int = map(int, current_version.split(".")) 
     40OrangeAddOn = namedtuple('OrangeAddOn', ['name', 'available_version', 'installed_version', 'summary', 'description', 
     41                                         'author', 'docs_url', 'keywords', 'homepage', 'package_url', 
     42                                         'release_url', 'release_size', 'python_version']) 
     43#It'd be great if we could somehow read a list and descriptions of widgets, show them in the dialog and enable 
     44#search of add-ons based on keywords in widget names and descriptions. 
     45 
     46INDEX_RE = "[^a-z0-9-']"  # RE for splitting entries in the search index 
     47 
     48AOLIST_FILE = os.path.join(Orange.utils.environ.orange_settings_dir, "addons.shelve") 
     49try: 
     50    addons = shelve.open(AOLIST_FILE, 'c') 
     51    if any(name != name.lower() for name, record in addons.items()):  # Try to read the whole list and check for sanity. 
     52        raise Exception("Corrupted add-on list.") 
     53except: 
     54    if os.path.isfile(AOLIST_FILE): 
     55        os.remove(AOLIST_FILE) 
     56    addons = shelve.open(AOLIST_FILE, 'n') 
     57 
     58addons_corrupted = len(addons)==0 
     59 
     60addon_refresh_callback = [] 
     61 
     62global index 
     63index = defaultdict(list) 
     64def rebuild_index(): 
     65    global index 
     66 
     67    index = defaultdict(list) 
     68    for name, ao in addons.items(): 
     69        for s in [name, ao.summary, ao.description, ao.author] + (ao.keywords if ao.keywords else []): 
     70            if not s: 
     71                continue 
     72            words = [word for word in re.split(INDEX_RE, s.lower()) 
     73                     if len(word)>1] 
     74            for word in words: 
     75                for i in range(len(word)): 
     76                    index[word[:i+1]].append(name) 
     77 
     78def search_index(query): 
     79    global index 
     80    result = set() 
     81    words = [word for word in re.split(INDEX_RE, query.lower()) if len(word)>1] 
     82    if not words: 
     83        return addons.keys() 
     84    for word in words: 
     85        result.update(index[word]) 
     86    return result 
     87 
     88def refresh_available_addons(force=False, progress_callback=None): 
     89    pypi = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') 
     90    if progress_callback: 
     91        progress_callback(1, 0) 
     92 
     93    pkg_dict = {} 
     94    for data in pypi.search({'keywords': 'orange'}): 
     95        name = data['name'] 
     96        order = data['_pypi_ordering'] 
     97        if name not in pkg_dict or pkg_dict[name][0] < order: 
     98            pkg_dict[name] = (order, data['version']) 
     99 
     100    try: 
     101        import slumber 
     102        readthedocs = slumber.API(base_url='http://readthedocs.org/api/v1/') 
    160103    except: 
    161         xmlver_int = [] 
    162     ver_int = map(int, version.split(".")) 
    163     if xmlver_int[:3] == ver_int[:3]: 
    164         version += ".%d" % ((xmlver_int[3] if len(xmlver_int)>3 else 0) +1) 
    165     return version 
    166  
    167 class OrangeRegisteredAddOn(): 
    168     """ 
    169     An add-on that is not linked to an on-line repository, but resides in an 
    170     independent directory and has been registered in Orange to be loaded when 
    171     Canvas is run. Helper methods are also implemented to enable packaging of 
    172     a registered add-on into an .oao package, including methods to generate 
    173     a skeleton of documentation files. 
    174      
    175     .. attribute:: id 
    176      
    177        ID of the add-on. IDs of registered add-ons are in form 
    178        "registered:<dir>", where <dir> is the directory of add-on's files. 
    179      
    180     .. attribute:: name 
    181         
    182        name of the add-on. 
    183         
    184     .. attribute:: directory 
    185      
    186        the directory where the add-on's files reside. 
    187      
    188     .. attribute:: systemwide 
    189      
    190        a flag indicating whether the add-on is registered system-wide, i.e. 
    191        for all OS users. 
    192      
    193     """ 
    194      
    195     def __init__(self, name, directory, systemwide=False): 
    196         """ 
    197         Constructor only sets the attributes. 
    198          
    199         :param name: name of the add-on. 
    200         :type name: str 
    201          
    202         :param directory: full path to the add-on's files. 
    203         :type directory: str 
    204          
    205         :param systemwide: determines whether the add-on is installed 
    206             systemwide, ie. for all users. 
    207         :type systemwide: boolean 
    208         """ 
    209         self.name = name 
    210         self.directory = directory 
    211         self.systemwide = systemwide 
    212          
    213         # Imitate real add-ons behaviour 
    214         self.id = "registered:"+directory 
    215  
    216     # Imitate real add-ons behaviour 
    217     def has_single_widget(self): 
    218         """ 
    219         Always return False: this feature is not implemented for registered 
    220         add-ons. 
    221         """ 
    222         return False 
    223  
    224     def directory_documentation(self): 
    225         """ 
    226         Return the documentation directory -- the "doc" directory under the 
    227         add-on's directory. 
    228         """ 
    229         return os.path.join(self.directory, "doc") 
    230  
    231     def uninstall(self, refresh=True): 
    232         """ 
    233         Uninstall, or rather unregister, the registered add-on. The files in 
    234         add-on's directory are not deleted or in any other way changed. 
    235          
    236         :param refresh: determines whether add-on list change callback 
    237             functions are to be called after the unregistration process. This 
    238             should always be True, except when multiple operations are executed 
    239             in a batch. 
    240         :type refresh: boolean 
    241         """ 
     104        readthedocs = None 
     105 
     106    docs = {} 
     107    if progress_callback: 
     108        progress_callback(len(pkg_dict)+1, 1) 
     109    for i, (name, (_, version)) in enumerate(pkg_dict.items()): 
     110        if force or name not in addons or addons[name.lower()].available_version != version: 
     111            try: 
     112                data = pypi.release_data(name, version) 
     113                rel = pypi.release_urls(name, version)[0] 
     114 
     115                if readthedocs: 
     116                    try: 
     117                        docs = readthedocs.project.get(slug=name.lower())['objects'][0] 
     118                    except: 
     119                        docs = {} 
     120                addons[name.lower()] = OrangeAddOn(name = name, 
     121                                           available_version = data['version'], 
     122                                           installed_version = addons[name.lower()].installed_version if name.lower() in addons else None, 
     123                                           summary = data['summary'], 
     124                                           description = data.get('description', ''), 
     125                                           author = str((data.get('author', '') or '') + ' ' + (data.get('author_email', '') or '')).strip(), 
     126                                           docs_url = data.get('docs_url', docs.get('subdomain', '')), 
     127                                           keywords = data.get('keywords', "").split(","), 
     128                                           homepage = data.get('home_page', ''), 
     129                                           package_url = data.get('package_url', ''), 
     130                                           release_url = rel.get('url', None), 
     131                                           release_size = rel.get('size', -1), 
     132                                           python_version = rel.get('python_version', None)) 
     133            except Exception, e: 
     134                import traceback 
     135                traceback.print_exc() 
     136                warnings.warn('Could not load data for the following add-on: %s'%name) 
     137        if progress_callback: 
     138            progress_callback(len(pkg_dict)+1, i+2) 
     139    addons_corrupted = False 
     140    addons.sync() 
     141 
     142    rebuild_index() 
     143 
     144def load_installed_addons(): 
     145    found = set() 
     146    for entry_point in pkg_resources.iter_entry_points(ADDONS_ENTRY_POINT): 
     147        name, version = entry_point.dist.project_name, entry_point.dist.version 
     148        #TODO We could import setup.py from entry_point.location and load descriptions and such ... 
     149        if name.lower() in addons: 
     150            addons[name.lower()] = addons[name.lower()]._replace(installed_version = version) 
     151        else: 
     152            addons[name.lower()] = OrangeAddOn(name = name, 
     153                available_version = None, 
     154                installed_version = version, 
     155                summary = "", 
     156                description = "", 
     157                author = "", 
     158                docs_url = "", 
     159                keywords = "", 
     160                homepage = "", 
     161                package_url = "", 
     162                release_url = "", 
     163                release_size = None, 
     164                python_version = None) 
     165        found.add(name.lower()) 
     166    for name in set(addons).difference(found): 
     167        addons[name.lower()] = addons[name.lower()]._replace(installed_version = None) 
     168    addons.sync() 
     169    rebuild_index() 
     170 
     171def run_setup(setup_script, args): 
     172    old_dir = os.getcwd() 
     173    save_argv = sys.argv[:] 
     174    save_path = sys.path[:] 
     175    setup_dir = os.path.abspath(os.path.dirname(setup_script)) 
     176    temp_dir = os.path.join(setup_dir,'temp') 
     177    if not os.path.isdir(temp_dir): os.makedirs(temp_dir) 
     178    save_tmp = tempfile.tempdir 
     179    save_modules = sys.modules.copy() 
     180    try: 
     181        tempfile.tempdir = temp_dir 
     182        os.chdir(setup_dir) 
    242183        try: 
    243             unregister_addon(self.name, self.directory, user_only=True)             
    244             if refresh: 
    245                 refresh_addons() 
    246             return True 
     184            sys.argv[:] = [setup_script]+list(args) 
     185            sys.path.insert(0, setup_dir) 
     186            execfile( 
     187                    "setup.py", 
     188                    {'__file__':setup_script, '__name__':'__main__'} 
     189                ) 
     190        except SystemExit, v: 
     191            if v.args and v.args[0]: 
     192                raise 
     193                # Normal exit, just return 
     194    finally: 
     195        sys.modules.update(save_modules) 
     196        for key in list(sys.modules): 
     197            if key not in save_modules: del sys.modules[key] 
     198        os.chdir(old_dir) 
     199        sys.path[:] = save_path 
     200        sys.argv[:] = save_argv 
     201        tempfile.tempdir = save_tmp 
     202 
     203 
     204def install(name, progress_callback=None): 
     205    if progress_callback: 
     206        progress_callback(1, 0) 
     207    import site 
     208    try: 
     209        import urllib 
     210        rh = (lambda done, bs, fs: progress_callback(fs/bs, done)) if progress_callback else None 
     211        egg = urllib.urlretrieve(addons[name.lower()].release_url, reporthook=rh)[0] 
     212    except Exception, e: 
     213        raise Exception("Unable to download add-on from repository: %s" % e) 
     214 
     215    try: 
     216        try: 
     217            tmpdir = tempfile.mkdtemp() 
     218            egg_contents = tarfile.open(egg) 
     219            egg_contents.extractall(tmpdir) 
     220            setup_py = os.path.join(tmpdir, name+'-'+addons[name.lower()].available_version, 'setup.py') 
    247221        except Exception, e: 
    248             raise InstallationException("Unable to unregister add-on: %s" % 
    249                                         (self.name, e)) 
    250  
    251     def prepare(self, id=None, name=42, version="auto", description=None, 
    252                 tags=None, author_organizations=None, author_creators=None, 
    253                 author_contributors=None, preferred_directory=None, 
    254                 homepage=None): 
    255         """ 
    256         Prepare the add-on for packaging into an .oao ZIP file and add the 
    257         necessary files to the add-on directory (possibly overwriting some!). 
    258  
    259         :param id: ID of the add-on. Must be a valid GUID; None means it is 
    260             retained from existing addon.xml if it exists, otherwise a new GUID 
    261             is generated. 
    262         :type id: str 
    263          
    264         :param name: name of the add-on; None retains existing value if it 
    265             exists and raises exception otherwise; the default value of 42 
    266             uses :obj:`self.name`. 
    267         :type name: str 
    268              
    269         :param version: version of the add-on. None retains existing value if 
    270             it exists and does the same as "auto" otherwise; "auto" generates a 
    271             new version number from the current date in format 'yyyy.mm.dd' 
    272             (see :obj:`Orange.utils.addons.suggest_version`); if that is equal 
    273             to the current version, another integer component is appended. 
    274         :type version: str 
    275          
    276         :param description: add-on's description. None retains existing value 
    277             if it exists and raises an exception otherwise. 
    278         :type description: str 
    279          
    280         :param tags: tags; None retains existing value if it exists, else 
    281             defaults to []. 
    282         :type tags: list of str 
    283          
    284         :param author_organizations: list of authoring organizations. None 
    285             retains existing value if it exists, else defaults to []. 
    286         :type author_organizations: list of str 
    287          
    288         :param author_creators: list of names of authors. None 
    289             retains existing value if it exists, else defaults to []. 
    290         :type author_creators: list of str 
    291  
    292         :param author_contributors: list of additional organizations or people 
    293             that have contributed to the add-on development. None 
    294             retains existing value if it exists, else defaults to []. 
    295         :type author_contributors: list of str 
    296  
    297         :param preferred_directory: default directory name for installation. 
    298             None retains existing value, "" removes the tag from the XML. 
    299         :type preferred_directory: str 
    300              
    301         :param homepage: the URL of add-on's website. None retains existing 
    302             value, "" removes the tag from the XML. 
    303         :type homepage: str 
    304         """ 
    305         ########################## 
    306         # addon.xml maintenance. # 
    307         ########################## 
    308         addon_xml_path = os.path.join(self.directory, "addon.xml") 
     222            raise Exception("Unable to unpack add-on: %s" % e) 
     223 
     224        if not os.path.isfile(setup_py): 
     225            raise Exception("Unable to install add-on - it is not properly packed.") 
     226 
    309227        try: 
    310             xmldoc = xml.dom.minidom.parse(addon_xml_path) 
     228            switches = [] 
     229            if site.USER_SITE in sys.path:   # we're not in a virtualenv 
     230                switches.append('--user') 
     231            run_setup(setup_py, ['install'] + switches) 
    311232        except Exception, e: 
    312             warnings.warn("Could not load addon.xml because \"%s\"; a new one "+ 
    313                           "will be created." % e, Warning, 0) 
    314             impl = xml.dom.minidom.getDOMImplementation() 
    315             xmldoc = impl.createDocument(None, "OrangeAddOn", None) 
    316         xmldoc_root = xmldoc.documentElement 
    317         # GUID 
    318         if not id and not xml_text_of("id", parent=xmldoc_root): 
    319             # GUID needs to be generated 
    320             import uuid 
    321             id = str(uuid.uuid1()) 
    322         if id: 
    323             xml_set(xmldoc_root, "id", id) 
    324         # name 
    325         if name==42: 
    326             name = self.name 
    327         if name and name.strip(): 
    328             xml_set(xmldoc_root, "name", name.strip()) 
    329         elif not xml_text_of("name", parent=xmldoc_root): 
    330             raise PackingException("'name' is a mandatory value!") 
    331         name = xml_text_of("name", parent=xmldoc_root) 
    332         # version 
    333         xml_version = xml_text_of("version", parent=xmldoc_root) 
    334         if not xml_version and not version: 
    335             version = "auto" 
    336         if version == "auto": 
    337             version = suggest_version(xml_version) 
    338         if version: 
    339             xml_set(xmldoc_root, "version", version) 
    340         # description 
    341         meta = get_element_nonrecursive(xmldoc_root, "meta", create=True) 
    342         if description and description.strip(): 
    343             xml_set(meta, "description", description.strip()) 
    344         elif not xml_text_of("description", parent=meta): 
    345             raise PackingException("'description' is a mandatory value!") 
    346         # tags 
    347         def update_list(root, node_name, list): 
    348             listNode = get_element_nonrecursive(root, node_name) 
    349             while listNode: 
    350                 root.removeChild(listNode) 
    351                 listNode = get_element_nonrecursive(root, node_name) 
    352             for value in list: 
    353                 root.appendChild(create_text_element(node_name, value)) 
    354         if tags!=None: 
    355             tags_node = get_element_nonrecursive(meta, "tags", create=True) 
    356             update_list(tags_node, "tag", tags) 
    357         # authors 
    358         if author_organizations!=None or author_contributors!=None or \ 
    359            author_creators!=None: 
    360             authorsNode = get_element_nonrecursive(meta, "authors", create=True) 
    361             if author_organizations!=None: update_list(authorsNode, 
    362                                                        "organization", 
    363                                                        author_organizations) 
    364             if author_creators!=None:      update_list(authorsNode, 
    365                                                        "creator", 
    366                                                        author_creators) 
    367             if author_contributors!=None:  update_list(authorsNode, 
    368                                                        "contributor", 
    369                                                        author_contributors) 
    370         #  preferred_directory 
    371         if preferred_directory != None: 
    372             xml_set(xmldoc_root, "preferred_directory", preferred_directory 
    373                     if preferred_directory else None) 
    374         #  homepage 
    375         if homepage != None: 
    376             xml_set(xmldoc_root, "homepage", homepage if homepage else None) 
    377              
    378         import codecs 
    379         xmldoc.writexml(codecs.open(addon_xml_path, 'w', "utf-8"), 
    380                         encoding="UTF-8") 
    381         sys.stderr.write("Updated addon.xml written.\n") 
    382  
    383         ########################## 
    384         # style.css creation     # 
    385         ########################## 
    386         localcss = os.path.join(self.directory_documentation(), "style.css") 
    387         orangecss = os.path.join(Orange.utils.environ.doc_install_dir, "style.css") 
    388         if not os.path.isfile(localcss): 
    389             if os.path.isfile(orangecss): 
    390                 import shutil 
    391                 shutil.copy(orangecss, localcss) 
    392                 sys.stderr.write("doc/style.css created.\n") 
    393             else: 
    394                 raise PackingException("Could not find style.css in orange"+\ 
    395                                        " documentation directory.") 
    396  
    397         ########################## 
    398         # index.html creation    # 
    399         ########################## 
    400         if not os.path.isdir(self.directory_documentation()): 
    401             os.mkdir(self.directory_documentation()) 
    402         hasIndex = False 
    403         for fname in ["main", "index", "default"]: 
    404             for ext in ["html", "htm"]: 
    405                 hasIndex = hasIndex or os.path.isfile(os.path.join(self.directory_documentation(), 
    406                                                                    fname+"."+ext)) 
    407         if not hasIndex: 
    408             indexFile = open( os.path.join(self.directory_documentation(), 
    409                                            "index.html"), 'w') 
    410             indexFile.write('<html><head><link rel="stylesheet" '+\ 
    411                             'href="style.css" type="text/css" /><title>%s'+\ 
    412                             '</title></head><body><h1>Module Documentation'+\ 
    413                             '</h1>%s</body></html>' % (name+" Orange Add-on "+ \ 
    414                                                        "Documentation", 
    415                             "This is where technical add-on module "+\ 
    416                             "documentation is. Well, at least it <i>should</i>"+\ 
    417                             " be.")) 
    418             indexFile.close() 
    419             sys.stderr.write("doc/index.html written.\n") 
    420              
    421         ########################## 
    422         # iconlist.html creation # 
    423         ########################## 
    424         wdocdir = os.path.join(self.directory_documentation(), "widgets") 
    425         if not os.path.isdir(wdocdir): os.mkdir(wdocdir) 
    426         open(os.path.join(wdocdir, "index.html"), 'w').write(self.iconlist_html()) 
    427         sys.stderr.write("Widget list (doc/widgets/index.html) written.\n") 
    428  
    429         ########################## 
    430         # copying the icons      # 
    431         ########################## 
    432         icondir = os.path.join(self.directory, "widgets", "icons") 
    433         icondocdir = os.path.join(wdocdir, "icons") 
    434         proticondir = os.path.join(self.directory, "widgets", "prototypes", 
    435                                    "icons") 
    436         proticondocdir = os.path.join(wdocdir, "prototypes", "icons") 
    437  
    438         import shutil 
    439         iconbg_file = os.path.join(Orange.utils.environ.icons_install_dir, "background_32.png") 
    440         iconun_file = os.path.join(Orange.utils.environ.icons_install_dir, "Unknown.png") 
    441         if not os.path.isdir(icondocdir): os.mkdir(icondocdir) 
    442         if os.path.isfile(iconbg_file): shutil.copy(iconbg_file, icondocdir) 
    443         if os.path.isfile(iconun_file): shutil.copy(iconun_file, icondocdir) 
    444          
    445         if os.path.isdir(icondir): 
    446             import distutils.dir_util 
    447             distutils.dir_util.copy_tree(icondir, icondocdir) 
    448         if os.path.isdir(proticondir): 
    449             import distutils.dir_util 
    450             if not os.path.isdir(os.path.join(wdocdir, "prototypes")): 
    451                 os.mkdir(os.path.join(wdocdir, "prototypes")) 
    452             if not os.path.isdir(proticondocdir): os.mkdir(proticondocdir) 
    453             distutils.dir_util.copy_tree(proticondir, proticondocdir) 
    454         sys.stderr.write("Widget icons copied to doc/widgets/.\n") 
    455  
    456  
    457     ##################################################### 
    458     # What follows are ugly HTML generators.            # 
    459     ##################################################### 
    460     def widget_doc_skeleton(self, widget, prototype=False): 
    461         """ 
    462         Return an HTML skeleton for documentation of a widget. 
    463          
    464         :param widget: widget metadata. 
    465         :type widget: :class:`widgetparser.WidgetMetaData` 
    466          
    467         :param prototype: determines, whether this is a prototype widget. This 
    468             is important to generate appropriate relative paths to the icons and 
    469             CSS. 
    470         :type prototype: boolean 
    471         """ 
    472         wfile = os.path.splitext(os.path.split(widget.filename)[1])[0][2:] 
    473         pathprefix = "../" if prototype else "" 
    474         iconcode = '\n<p><img class="screenshot" style="z-index:2; border: none; height: 32px; width: 32px; position: relative" src="%s" title="Widget: %s" width="32" height="32" /><img class="screenshot" style="margin-left:-32px; z-index:1; border: none; height: 32px; width: 32px; position: relative" src="%sicons/background_32.png" width="32" height="32" /></p>' % (widget.icon, widget.name, pathprefix) 
    475          
    476         inputscode = """<DT>(None)</DT>""" 
    477         outputscode = """<DT>(None)</DT>""" 
    478         il, ol = eval(widget.inputList), eval(widget.outputList) 
    479         if il: 
    480             inputscode = "\n".join(["<dt>%s (%s)</dt>\n<dd>Describe here, what this input does.</dd>\n" % (p[0], p[1]) for p in il]) 
    481         if ol: 
    482             outputscode = "\n".join(["<dt>%s (%s)</dt>\n<dd>Describe here, what this output does.</dd>\n" % (p[0], p[1]) for p in ol]) 
    483         html = """<html> 
    484 <head> 
    485 <title>%s</title> 
    486 <link rel=stylesheet href="%s../style.css" type="text/css" media=screen> 
    487 </head> 
    488  
    489 <body> 
    490  
    491 <h1>%s</h1> 
    492 %s 
    493 <p>This widget does this and that..</p> 
    494  
    495 <h2>Channels</h2> 
    496  
    497 <h3>Inputs</h3> 
    498  
    499 <dl class=attributes> 
    500 %s 
    501 </dl> 
    502  
    503 <h3>Outputs</h3> 
    504 <dl class=attributes> 
    505 %s 
    506 </dl> 
    507  
    508 <h2>Description</h2> 
    509  
    510 <!-- <img class="leftscreenshot" src="%s.png" align="left"> --> 
    511  
    512 <p>This is a widget which ...</p> 
    513  
    514 <p>If you press <span class="option">Reload</span>, something will happen. <span class="option">Commit</span> button does something else.</p> 
    515  
    516 <h2>Examples</h2> 
    517  
    518 <p>This widget is used in this and that way. It often gets data from 
    519 the <a href="Another.htm">Another Widget</a>.</p> 
    520  
    521 <!-- <img class="schema" src="%s-Example.png" alt="Schema with %s widget"> --> 
    522  
    523 </body> 
    524 </html>""" % (widget.name, pathprefix, widget.name, iconcode, inputscode, 
    525               outputscode, wfile, wfile, widget.name) 
    526         return html 
    527          
    528      
    529     def iconlist_html(self, create_skeleton_docs=True): 
    530         """ 
    531         Prepare and return an HTML document, containing a table of widget icons. 
    532          
    533         :param create_skeleton_docs: determines whether documentation skeleton for 
    534             widgets without documentation should be generated (ie. whether the 
    535             method :obj:`widget_doc_skeleton` should be called. 
    536         :type create_skeleton_docs: boolean 
    537         """ 
    538         html = """ 
    539 <style> 
    540 div#maininner { 
    541   padding-top: 25px; 
    542 } 
    543  
    544 div.catdiv h2 { 
    545   border-bottom: none; 
    546   padding-left: 20px; 
    547   padding-top: 5px; 
    548   font-size: 14px; 
    549   margin-bottom: 5px; 
    550   margin-top: 0px; 
    551   color: #fe6612; 
    552 } 
    553  
    554 div.catdiv { 
    555   margin-left: 10px; 
    556   margin-right: 10px; 
    557   margin-bottom: 20px; 
    558   background-color: #eeeeee; 
    559 } 
    560  
    561 div.catdiv table { 
    562   width: 98%; 
    563   margin: 10px; 
    564   padding-right: 20px; 
    565 } 
    566  
    567 div.catdiv table td { 
    568   background-color: white; 
    569 /*  height: 18px;*/ 
    570   margin: 25px; 
    571   vertical-align: center; 
    572   border-left: solid #eeeeee 10px; 
    573   border-bottom: solid #eeeeee 3px; 
    574   font-size: 13px; 
    575 } 
    576  
    577 div.catdiv table td.left { 
    578   width: 3%; 
    579   height: 28px; 
    580   padding: 0; 
    581   margin: 0; 
    582 } 
    583  
    584 div.catdiv table td.left-nodoc { 
    585   width: 3%; 
    586   color: #aaaaaa; 
    587   padding: 0; 
    588   margin: 0 
    589 } 
    590  
    591  
    592 div.catdiv table td.right { 
    593   padding-left: 5px; 
    594   border-left: none; 
    595   width: 22%; 
    596   font-size: 11px; 
    597 } 
    598  
    599 div.catdiv table td.right-nodoc { 
    600   width: 22%; 
    601   padding-left: 5px; 
    602   border-left: none; 
    603   color: #aaaaaa; 
    604   font-size: 11px; 
    605 } 
    606  
    607 div.catdiv table td.empty { 
    608   background-color: #eeeeee; 
    609 } 
    610  
    611  
    612 .rnd1 { 
    613  height: 1px; 
    614  border-left: solid 3px #ffffff; 
    615  border-right: solid 3px #ffffff; 
    616  margin: 0px; 
    617  padding: 0px; 
    618 } 
    619  
    620 .rnd2 { 
    621  height: 2px; 
    622  border-left: solid 1px #ffffff; 
    623  border-right: solid 1px #ffffff; 
    624  margin: 0px; 
    625  padding: 0px; 
    626 } 
    627  
    628 .rnd11 { 
    629  height: 1px; 
    630  border-left: solid 1px #eeeeee; 
    631  border-right: solid 1px #eeeeee; 
    632  margin: 0px; 
    633  padding: 0px; 
    634 } 
    635  
    636 .rnd1l { 
    637  height: 1px; 
    638  border-left: solid 1px white; 
    639  border-right: solid 1px #eeeeee; 
    640  margin: 0px; 
    641  padding: 0px; 
    642 } 
    643  
    644 div.catdiv table img { 
    645   border: none; 
    646   height: 28px; 
    647   width: 28px; 
    648   position: relative; 
    649 } 
    650 </style> 
    651  
    652 <script> 
    653 function setElColors(t, id, color) { 
    654   t.style.backgroundColor=document.getElementById('cid'+id).style.backgroundColor = color; 
    655 } 
    656 </script> 
    657  
    658 <p style="font-size: 16px; font-weight: bold">Catalog of widgets</p> 
    659         """ 
    660         wdir = os.path.join(self.directory, "widgets") 
    661         pdir = os.path.join(wdir, "prototypes") 
    662         widgets = {} 
    663         for (prototype, filename) in [(False, filename) for filename in 
    664                                       glob.iglob(os.path.join(wdir, "*.py"))] +\ 
    665                                      [(True, filename) for filename in 
    666                                       glob.iglob(os.path.join(pdir, "*.py"))]: 
    667             if os.path.isdir(filename): 
    668                 continue 
    669             try: 
    670                 meta =widgetparser.WidgetMetaData(file(filename).read(), 
    671                                                    "Prototypes" if prototype else "Uncategorized", 
    672                                                    enforceDefaultCategory=prototype, 
    673                                                    filename=filename) 
    674             except: 
    675                 continue # Probably not an Orange Widget module; skip this file. 
    676             if meta.category in widgets: 
    677                 widgets[meta.category].append((prototype, meta)) 
    678             else: 
    679                 widgets[meta.category] = [(prototype, meta)] 
    680         category_list = [cat for cat in widgets.keys() 
    681                          if cat not in ["Prototypes", "Uncategorized"]] 
    682         category_list.sort() 
    683         for cat in ["Uncategorized"] + category_list + ["Prototypes"]: 
    684             if cat not in widgets: 
    685                 continue 
    686             html += """    <div class="catdiv"> 
    687     <div class="rnd1"></div> 
    688     <div class="rnd2"></div> 
    689  
    690     <h2>%s</h2> 
    691     <table><tr> 
    692 """ % cat 
    693             for i, (p, w) in enumerate(widgets[cat]): 
    694                 if (i>0) and (i%4 == 0): 
    695                     html += "</tr><tr>\n" 
    696                 wreldir = os.path.relpath(os.path.split(w.filename)[0], wdir)\ 
    697                           if "relpath" in os.path.__dict__ else\ 
    698                           os.path.split(w.filename)[0].replace(wdir, "") 
    699                 docfile = os.path.join(wreldir, 
    700                                        os.path.splitext(os.path.split(w.filename)[1][2:])[0] + ".htm") 
    701                  
    702                 iconfile = os.path.join(wreldir, w.icon) 
    703                 if not os.path.isfile(os.path.join(wdir, iconfile)): 
    704                     iconfile = "icons/Unknown.png" 
    705                 if os.path.isfile(os.path.join(self.directory_documentation(), 
    706                                                "widgets", docfile)): 
    707                     html += """<td id="cid%d" class="left" 
    708       onmouseover="this.style.backgroundColor='#fff7df'" 
    709       onmouseout="this.style.backgroundColor=null" 
    710       onclick="this.style.backgroundColor=null; window.location='%s'"> 
    711       <div class="rnd11"></div> 
    712       <img style="z-index:2" src="%s" title="Widget: Text File" width="28" height="28" /><img style="margin-left:-28px; z-index:1" src="icons/background_32.png" width="28" height="28" /> 
    713       <div class="rnd11"></div> 
    714   </td> 
    715  
    716   <td class="right" 
    717     onmouseover="setElColors(this, %d, '#fff7df')" 
    718     onmouseout="setElColors(this, %d, null)" 
    719     onclick="setElColors(this, %d, null); window.location='%s'"> 
    720       %s 
    721 </td> 
    722 """ % (i, docfile, iconfile, i, i, i, docfile, w.name) 
    723                 else: 
    724                     skeleton_filename = os.path.join(self.directory_documentation(), 
    725                                                      "widgets", 
    726                                                      docfile+".skeleton") 
    727                     if not os.path.isdir(os.path.dirname(skeleton_filename)): 
    728                         os.mkdir(os.path.dirname(skeleton_filename)) 
    729                     open(skeleton_filename, 'w').write(self.widget_doc_skeleton(w, prototype=p)) 
    730                     html += """  <td id="cid%d" class="left-nodoc"> 
    731       <div class="rnd11"></div> 
    732       <img style="z-index:2" src="%s" title="Widget: Text File" width="28" height="28" /><img style="margin-left:-28px; z-index:1" src="icons/background_32.png" width="28" height="28" /> 
    733       <div class="rnd11"></div> 
    734   </td> 
    735   <td class="right-nodoc"> 
    736       <div class="rnd1l"></div> 
    737       %s 
    738       <div class="rnd1l"></div> 
    739  
    740   </td> 
    741 """ % (i, iconfile, w.name) 
    742             html += '</tr></table>\n<div class="rnd2"></div>\n<div class="rnd1"></div>\n</div>\n' 
    743         return html 
    744     ########################################################################### 
    745     # Here end the ugly HTML generators. Only beautiful code from now on! ;) # 
    746     ########################################################################### 
    747          
    748  
    749 class OrangeAddOn(): 
    750     """ 
    751     Stores data about an add-on for Orange.  
    752  
    753     .. attribute:: id 
    754      
    755        ID of the add-on. IDs of registered add-ons are in form 
    756        "registered:<dir>", where <dir> is the directory of add-on's files. 
    757      
    758     .. attribute:: name 
    759         
    760        name of the add-on. 
    761         
    762     .. attribute:: architecture 
    763      
    764        add-on structure version; currently it must have a value of 1. 
    765      
    766     .. attribute:: homepage 
    767      
    768        URL of add-on's web site. 
    769         
    770     .. attribute:: version_str 
    771         
    772        string representation of add-on's version; must be a period-separated 
    773        list of integers. 
    774         
    775     .. attribute:: version 
    776      
    777        parsed value of the :obj:`version_str` attribute - a list of integers. 
    778      
    779     .. attribute:: description 
    780      
    781        textual description of the add-on. 
    782         
    783     .. attribute:: tags 
    784      
    785        textual tags that describe the add-on - a list of strings. 
    786      
    787     .. attribute:: author_organizations 
    788      
    789        a list of strings with names of organizations that developed the add-on. 
    790  
    791     .. attribute:: author_creators 
    792      
    793        a list of strings with names of individuals (persons) that developed the 
    794        add-on. 
    795  
    796     .. attribute:: author_contributors 
    797      
    798        a list of strings with names of organizations and individuals (persons) 
    799        that have made minor contributions to the add-on. 
    800      
    801     .. attribute:: preferred_directory 
    802      
    803        preferred name of the subdirectory under which the add-on is to be 
    804        installed. It is not guaranteed this directory name will be used; for 
    805        example, when such a directory already exists, another name will be 
    806        generated during installation. 
    807     """ 
    808  
    809     def __init__(self, xmlfile=None): 
    810         """ 
    811         Initialize an empty add-on descriptor. Initializes attributes with data 
    812         from an optionally passed XML add-on descriptor; otherwise sets all 
    813         attributes to None or, in case of list attributes, an empty list. 
    814          
    815         :param xmlfile: an optional file name or an instance of minidom's 
    816             Element with XML add-on descriptor. 
    817         :type xmlfile: :class:`xml.dom.minidom.Element` or str or 
    818             :class:`NoneType` 
    819         """ 
    820         self.name = None 
    821         self.architecture = None 
    822         self.homepage = None 
    823         self.id = None 
    824         self.version_str = None 
    825         self.version = None 
    826          
    827         self.description = None 
    828         self.tags = [] 
    829         self.author_organizations = [] 
    830         self.author_creators = [] 
    831         self.author_contributors = [] 
    832          
    833         self.preferred_directory = None 
    834          
    835         self.widgets = []  # List of widgetparser.WidgetMetaData objects 
    836          
    837         if xmlfile: 
    838             xml_doc_root = xmlfile if xmlfile.__class__ is xml.dom.minidom.Element else\ 
    839                          xml.dom.minidom.parse(xmlfile).documentElement 
    840             try: 
    841                 self.parsexml(xml_doc_root) 
    842             finally: 
    843                 xml_doc_root.unlink() 
    844  
    845     def clone(self, new=None): 
    846         """ 
    847         Clone the add-on descriptor, effectively making a deep copy. 
    848          
    849         :param new: a new instance of this class into which to copy the values 
    850             of attributes; if None, a new instance is constructed. 
    851         :type new: :class:`OrangeAddOn` or :class:`NoneType` 
    852         """ 
    853         if not new: 
    854             new = OrangeAddOn() 
    855         new.name = self.name 
    856         new.architecture = self.architecture 
    857         new.homepage = self.homepage 
    858         new.id = self.id 
    859         new.version_str = self.version_str 
    860         new.version = list(self.version) 
    861         new.description = self.description 
    862         new.tags = list(self.tags) 
    863         new.author_organizations = list(self.author_organizations) 
    864         new.author_creator = list(self.author_creators) 
    865         new.author_contributors = list(self.author_contributors) 
    866         new.prefferedDirectory = self.preferred_directory 
    867         new.widgets = [w.clone() for w in self.widgets] 
    868         return new 
    869  
    870     def directory_documentation(self): 
    871         """ 
    872         Return the documentation directory -- the "doc" directory under the 
    873         add-on's directory. 
    874         """ 
    875         #TODO This might be redefined in orngConfiguration. 
    876         return os.path.join(self.directory, "doc") 
    877  
    878     def parsexml(self, root): 
    879         """ 
    880         Parse the add-on's XML descriptor and set object's attributes 
    881         accordingly. 
    882          
    883         :param root: root of the add-on's descriptor (the node with tag name 
    884             "OrangeAddOn"). 
    885         :type root: :class:`xml.dom.minidom.Element` 
    886         """ 
    887         if root.tagName != "OrangeAddOn": 
    888             raise Exception("Invalid XML add-on descriptor: wrong root element name!") 
    889          
    890         mandatory = ["id", "architecture", "name", "version", "meta"] 
    891         textnodes = {"id": "id", "architecture": "architecture", "name": "name", 
    892                      "version": "version_str",  
    893                      "preferredDirectory": "preferredDirectory", 
    894                      "homePage": "homepage"} 
    895         for node in [n for n in root.childNodes if n.nodeType==n.ELEMENT_NODE]: 
    896             if node.tagName in mandatory: 
    897                 mandatory.remove(node.tagName) 
    898                  
    899             if node.tagName in textnodes: 
    900                 setattr(self, textnodes[node.tagName], 
    901                         widgetparser.xml_text_of(node)) 
    902             elif node.tagName == "meta": 
    903                 for node in [n for n in node.childNodes 
    904                              if n.nodeType==n.ELEMENT_NODE]: 
    905                     if node.tagName == "description": 
    906                         self.description = widgetparser.xml_text_of(node, True) 
    907                     elif node.tagName == "tags": 
    908                         for tagNode in [n for n in node.childNodes 
    909                                         if n.nodeType==n.ELEMENT_NODE and 
    910                                         n.tagName == "tag"]: 
    911                             self.tags.append(widgetparser.xml_text_of(tagNode)) 
    912                     elif node.tagName == "authors": 
    913                         authorTypes = {"organization": self.author_organizations, 
    914                                        "creator": self.author_creators, 
    915                                        "contributor": self.author_contributors} 
    916                         for authorNode in [n for n in node.childNodes 
    917                                            if n.nodeType==n.ELEMENT_NODE and 
    918                                            n.tagName in authorTypes]: 
    919                             authorTypes[authorNode.tagName].append(widgetparser.xml_text_of(authorNode)) 
    920             elif node.tagName == "widgets": 
    921                 for node in [n for n in node.childNodes 
    922                              if n.nodeType==n.ELEMENT_NODE]: 
    923                     if node.tagName == "widget": 
    924                         self.widgets.append(widgetparser.WidgetMetaData(node)) 
    925          
    926         if "afterparse" in self.__class__.__dict__: 
    927             self.afterparse(root) 
    928          
    929         self.validate_architecture() 
    930         if mandatory: 
    931             raise Exception("Mandatory elements missing: "+", ".join(mandatory)) 
    932         self.validate_id() 
    933         self.validate_name() 
    934         self.validate_version() 
    935         self.validate_description() 
    936         if self.preferred_directory==None: 
    937             self.preferred_directory = self.name 
    938  
    939     def validate_architecture(self): 
    940         """ 
    941         Raise an exception if the :obj:`architecture` (structure of the add-on) 
    942         is not supported. Currently, only architecture 1 exists. 
    943         """ 
    944         if self.architecture != "1": 
    945             raise Exception("Only architecture '1' is supported by current Orange!") 
    946      
    947     def validate_id(self): 
    948         """ 
    949         Raise an exception if the :obj:`id` is not a valid GUID. 
    950         """ 
    951         idPattern = re.compile("[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}") 
    952         if not idPattern.match(self.id): 
    953             raise Exception("Invalid ID!") 
    954  
    955     def validate_name(self): 
    956         """ 
    957         Raise an exception if the :obj:`name` is empty (or contains only 
    958         whitespace). 
    959         """ 
    960         if self.name.strip() == "": 
    961             raise Exception("Name is a mandatory field!") 
    962      
    963     def validate_version(self): 
    964         """ 
    965         Parse the :obj:`version_str` and populate the :obj:`version` attribute. 
    966         Raise an exception if the version is not in correct format (ie. a 
    967         period-separated list of integers). 
    968         """ 
    969         self.version = []   
    970         for sub in self.version_str.split("."): 
    971             try: 
    972                 self.version.append(int(sub)) 
    973             except: 
    974                 self.version = [] 
    975                 raise Exception("Invalid version string: '%s' is not an integer!" % sub) 
    976         self.version_str = ".".join(map(str,self.version)) 
    977              
    978     def validate_description(self): 
    979         """ 
    980         Raise an exception if the :obj:`description` is empty (or contains only 
    981         whitespace). 
    982         """ 
    983         if self.name.strip() == "": 
    984             raise Exception("Description is a mandatory field!") 
    985          
    986     def has_single_widget(self): 
    987         """ 
    988         Determine whether the add-on contains less than two widgets. 
    989         """ 
    990         return len(self.widgets) < 2 
    991          
    992  
    993 class OrangeAddOnInRepo(OrangeAddOn): 
    994     """ 
    995     Stores data about an add-on for Orange that exists in a repository. 
    996     Additional attributes are: 
    997      
    998     .. attribute:: repository 
    999      
    1000     A repository object (instance of :class:`OrangeAddOnRepository`) that 
    1001     contains data about the add-on's repository. 
    1002  
    1003     .. attribute:: filename 
    1004      
    1005     The name of .oao file in repository. 
    1006      
    1007     """ 
    1008       
    1009     def __init__(self, repository, filename=None, xmlfile=None): 
    1010         """ 
    1011         Constructor only sets the attributes. 
    1012          
    1013         :param repository: the repository that contains the add-on. 
    1014         :type repostitory: :class:`OrangeAddOnRepository` 
    1015          
    1016         :param filename: name of the .oao file in repository (is used only if 
    1017             the XML file does not specify the filename). 
    1018         :type filename: str 
    1019          
    1020         :param xmlfile: an optional file name or an instance of minidom's 
    1021             Element with XML add-on descriptor. 
    1022         :type xmlfile: :class:`xml.dom.minidom.Element` or str or 
    1023             :class:`NoneType` 
    1024         """ 
    1025         OrangeAddOn.__init__(self, xmlfile) 
    1026         self.repository = repository 
    1027         if "filename" not in self.__dict__: 
    1028             self.filename = filename 
    1029      
    1030     def afterparse(self, xml_root):  # Called by OrangeAddOn.parsexml() 
    1031         """ 
    1032         Read the filename attribute from the XML. This method is called by 
    1033         :obj:`OrangeAddOn.parsexml`. 
    1034         """ 
    1035         if xml_root.hasAttribute("filename"): 
    1036             self.filename = xml_root.getAttribute("filename") 
    1037              
    1038     def clone(self, new=None): 
    1039         """ 
    1040         Clone the add-on descriptor, effectively making a deep copy. 
    1041          
    1042         :param new: a new instance of this class into which to copy the values 
    1043             of attributes; if None, a new instance is constructed. 
    1044         :type new: :class:`OrangeAddOn` or :class:`NoneType` 
    1045         """ 
    1046         if not new: 
    1047             new = OrangeAddOnInRepo(self.repository) 
    1048         new.filename = self.filename 
    1049         return OrangeAddOn.clone(self, new) 
    1050  
    1051 class OrangeAddOnInstalled(OrangeAddOn): 
    1052     """ 
    1053     Stores data about an add-on for Orange that has been installed from a 
    1054     repository. Additional attribute is: 
    1055      
    1056     .. attribute:: directory 
    1057      
    1058     Directory of add-on's files. 
    1059      
    1060     """ 
    1061     def __init__(self, directory): 
    1062         """ 
    1063         Constructor only sets the attributes. 
    1064          
    1065         :param directory: directory of add-on's files, including an XML 
    1066             descriptor to read. 
    1067         :type directory: str 
    1068         """ 
    1069         OrangeAddOn.__init__(self, os.path.join(directory, "addon.xml") 
    1070                              if directory else None) 
    1071         self.directory = directory 
    1072      
    1073     def uninstall(self, refresh=True): 
    1074         """ 
    1075         Uninstall the installed add-on. WARNING: all files in add-on's directory 
    1076         are deleted! 
    1077          
    1078         :param refresh:  determines whether add-on list change callback 
    1079             functions are to be called after the unregistration process. This 
    1080             should always be True, except when multiple operations are executed 
    1081             in a batch. 
    1082         :type refresh: boolean 
    1083         """ 
    1084         try: 
    1085             _deltree(self.directory) 
    1086             del installed_addons[self.id] 
    1087             if refresh: 
    1088                 refresh_addons() 
    1089             return True 
    1090         except Exception, e: 
    1091             raise InstallationException("Unable to remove add-on: %s" % 
    1092                                         (self.name, e)) 
    1093          
    1094     def clone(self, new=None): 
    1095         """ 
    1096         Clone the add-on descriptor, effectively making a deep copy. 
    1097          
    1098         :param new: a new instance of this class into which to copy the values 
    1099             of attributes; if None, a new instance is constructed. 
    1100         :type new: :class:`OrangeAddOn` or :class:`NoneType` 
    1101         """ 
    1102         if not new: 
    1103             new = OrangeAddOnInstalled(None) 
    1104         new.directory = self.directory 
    1105         return OrangeAddOn.clone(self, new) 
    1106          
    1107 available_addons = {}  # RepositoryURL -> OrangeAddOnRepository object  
    1108 installed_addons = {}  # ID -> OrangeAddOnInstalled object 
    1109 registered_addons = [] # OrangeRegisteredAddOn objects 
    1110  
    1111 class RepositoryException(Exception): 
    1112     """ 
    1113     An exception that occurs during access to repository location. Behaves 
    1114     exactly as :class:`Exception`. 
    1115  
    1116     """ 
    1117     pass 
    1118  
    1119 global index_re 
    1120 index_re = "[^a-z0-9-']"  # RE for splitting entries in the search index 
    1121  
    1122 class OrangeAddOnRepository: 
    1123     """ 
    1124     Repository of Orange add-ons. 
    1125      
    1126     .. attribute:: name 
    1127      
    1128     A local descriptive name for the repository. 
    1129      
    1130     .. attribute:: url 
    1131      
    1132     URL of the repository root; http and file protocols are supported. 
    1133      
    1134     .. attribute:: addons 
    1135      
    1136     A dictionary mapping GUIDs to lists of add-on objects (of class 
    1137     :class:`OrangeAddOnInRepo`). Each GUID is thus mapped to at least one, 
    1138     but possibly more, different versions of add-on. 
    1139      
    1140     .. attribute:: index 
    1141      
    1142     A search index: sorted list of tuples (s, GUID), where such an entry 
    1143     signifies that when searching for a string that s starts with, add-on with 
    1144     the given GUID should be among results. 
    1145      
    1146     .. attribute:: last_refresh_utc 
    1147      
    1148     :obj:`time.time` of the last reloading of add-on list. 
    1149      
    1150     .. attribute:: has_web_script 
    1151      
    1152     A boolean indicating whether this is an http repository that contains the 
    1153     appropriate server-side python script that returns an XML with a list of 
    1154     add-ons. 
    1155      
    1156     """ 
    1157      
    1158     def __init__(self, name, url, load=True, force=False): 
    1159         """ 
    1160         :param name: a local descriptive name for the repository. 
    1161         :type name: str 
    1162          
    1163         :param url: URL of the repository root; http and file protocols are 
    1164             supported. If the protocol is not given, file:// is assumed. 
    1165         :type url: str 
    1166          
    1167         :param load: determines whether the list of repository's add-ons should 
    1168             be loaded immediately. 
    1169         :type load: boolean 
    1170          
    1171         :param force: determines whether loading of repository's add-on list 
    1172             is mandatory, ie. if an exception is to be raised in case of 
    1173             connection failure. 
    1174         :type force: boolean 
    1175         """ 
    1176          
    1177         self.name = name 
    1178         self.url = url 
    1179         self.checkurl() 
    1180         self.addons = {} 
    1181         self.index = [] 
    1182         self.last_refresh_utc = 0 
    1183         self._refresh_index() 
    1184         self.has_web_script = False 
    1185         if load: 
    1186             try: 
    1187                 self.refreshdata(True, True) 
    1188             except Exception, e: 
    1189                 if force: 
    1190                     warnings.warn("Couldn't load data from repository '%s': %s" 
    1191                                   % (self.name, e), Warning, 0) 
    1192                     return 
    1193                 raise e 
    1194          
    1195     def clone(self, new=None): 
    1196         """ 
    1197         Clone the repository descriptor, effectively making a deep copy. 
    1198          
    1199         :param new: a new instance of this class into which to copy the values 
    1200             of attributes; if None, a new instance is constructed. 
    1201         :type new: :class:`OrangeAddOnRepository` or :class:`NoneType` 
    1202         """ 
    1203         if not new: 
    1204             new = OrangeAddOnRepository(self.name, self.url, load=False) 
    1205         new.addons = {} 
    1206         for (id, versions) in self.addons.items(): 
    1207             new.addons[id] = [ao.clone() for ao in versions] 
    1208         new.index = list(self.index) 
    1209         new.last_refresh_utc = self.last_refresh_utc 
    1210         new.has_web_script = self.has_web_script if hasattr(self, 'has_web_script') else False 
    1211         return new 
    1212  
    1213     def checkurl(self): 
    1214         """ 
    1215         Check the URL for validity. Return True if it begins with "file://" or 
    1216         "http://" or if it does not specify a protocol (in this case, file:// is 
    1217         assumed). 
    1218         """ 
    1219         supportedProtocols = ["file", "http"] 
    1220         if "://" not in self.url: 
    1221             self.url = "file://"+self.url 
    1222         protocol = self.url.split("://")[0] 
    1223         if protocol not in supportedProtocols: 
    1224             raise Exception("Unable to load repository data: protocol '%s' not supported!" % 
    1225                             protocol) 
    1226  
    1227     def _add_addon(self, addon): 
    1228         """ 
    1229         Add the given addon descriptor to the :obj:`addons` dictionary. 
    1230         Operation is sucessful only if there is no add-on with equal GUID 
    1231         (:obj:`OrangeAddOn.id`) and version 
    1232         (:obj:`OrangeAddOn.version`) already in this repository. 
    1233          
    1234         :param addon: add-on descriptor to add. 
    1235         :type addon: :class:`OrangeAddOnInRepo` 
    1236         """ 
    1237         if addon.id in self.addons: 
    1238             versions = self.addons[addon.id] 
    1239             for version in versions: 
    1240                 if version.version == addon.version: 
    1241                     warnings.warn("Ignoring the second occurence of addon '%s'"+ 
    1242                                   ", version '%s'." % (addon.name, 
    1243                                                        addon.version_str), 
    1244                                   Warning, 0) 
    1245                     return 
    1246             versions.append(addon) 
    1247         else: 
    1248             self.addons[addon.id] = [addon] 
    1249  
    1250     def _add_packed_addon(self, oaofile, filename=None): 
    1251         """ 
    1252         Given a local path to an .oao file, add the addon descriptor to the 
    1253         :obj:`addons` dictionary. Specifically, "addon.xml" manifest is unpacked 
    1254         from the .oao, an :class:`OrangeAddOnInRepo` instance is constructed 
    1255         and :obj:`_add_addon` is invoked. 
    1256          
    1257         :param oaofile: path to the .oao file. 
    1258         :type oaofile: str 
    1259          
    1260         :param filename: name of the .oao file within the repository. 
    1261         :type filename: str 
    1262         """ 
    1263         pack = ZipFile(oaofile, 'r') 
    1264         try: 
    1265             manifestfile = _zip_open(pack, 'addon.xml') 
    1266             manifest = xml.dom.minidom.parse(manifestfile).documentElement 
    1267             manifest.appendChild(widgetparser.widgets_xml(pack)) 
    1268             addon = OrangeAddOnInRepo(self, filename, xmlfile=manifest) 
    1269             self._add_addon(addon) 
    1270         except Exception, e: 
    1271             raise Exception("Unable to load add-on descriptor: %s" % e) 
    1272      
    1273     def refreshdata(self, force=False, firstload=False, interval=3600*24): 
    1274         """ 
    1275         Refresh the add-on list if necessary. For an http repository, the 
    1276         server-side python script is invoked. If that fails, or if the 
    1277         repository is on local filesystem (file://), all .oao files are 
    1278         downloaded, unpacked and their manifests (addon.xml) are parsed. 
    1279          
    1280         :param force: force a refresh, even if less than a preset amount of 
    1281             time (see parameter :obj:`interval`) has passed since last refresh 
    1282             (see attribute :obj:`last_refresh_utc`). 
    1283         :type force: boolean 
    1284          
    1285         :param firstload: determines, whether this is the first loading of 
    1286             repository's contents. Right now, the only difference is that when 
    1287             there is no server-side repository script on an http repository and 
    1288             there are also no .oao files, this results in an exception if 
    1289             this parameter is set to True, and in a warning otherwise. 
    1290         :type firstload: boolean 
    1291          
    1292         :parameter interval: an amount of time in seconds that must pass since 
    1293             last refresh (:obj:`last_refresh_utc`) to make the refresh happen. 
    1294         :type interval: int 
    1295         """ 
    1296         if force or (self.last_refresh_utc < time.time() - interval): 
    1297             self.last_refresh_utc = time.time() 
    1298             self.has_web_script = False 
    1299             try: 
    1300                 protocol = self.url.split("://")[0] 
    1301                 if protocol == "http": # A remote repository 
    1302                     # Try to invoke a server-side script to retrieve add-on index (and therefore avoid downloading archives) 
    1303                     repositoryXmlDoc = None 
    1304                     try: 
    1305                         repositoryXmlDoc = urllib2.urlopen(self.url+"/addOnServer.py?machine=1") 
    1306                         repositoryXml = xml.dom.minidom.parse(repositoryXmlDoc).documentElement 
    1307                         if repositoryXml.tagName != "OrangeAddOnRepository": 
    1308                             raise Exception("Invalid XML add-on repository descriptor: wrong root element name!") 
    1309                         self.addons = {} 
    1310                         for (i, node) in enumerate([n for n 
    1311                                                     in repositoryXml.childNodes 
    1312                                                     if n.nodeType==n.ELEMENT_NODE]): 
    1313                             if node.tagName == "OrangeAddOn": 
    1314                                 try: 
    1315                                     addon = OrangeAddOnInRepo(self, xmlfile=node) 
    1316                                     self._add_addon(addon) 
    1317                                 except Exception, e: 
    1318                                     warnings.warn("Ignoring node nr. %d in "+ 
    1319                                                   "repository '%s' because of"+ 
    1320                                                   " an error: %s" % (i+1, 
    1321                                                                      self.name, 
    1322                                                                      e), 
    1323                                                   Warning, 0) 
    1324                         self.has_web_script = True 
    1325                         return True 
    1326                     except Exception, e: 
    1327                         warnings.warn("A problem occurred using server-side script on repository '%s': %s.\nAll add-ons need to be downloaded for their metadata to be extracted!" 
    1328                                       % (self.name, str(e)), Warning, 0) 
    1329  
    1330                     # Invoking script failed - trying to get and parse a directory listing 
    1331                     try: 
    1332                         repoconn = urllib2.urlopen(self.url+'abc') 
    1333                         response = "".join(repoconn.readlines()) 
    1334                     except Exception, e: 
    1335                         raise RepositoryException("Unable to load repository data: %s" % e) 
    1336                     addOnFiles = map(lambda x: x.split('"')[1], 
    1337                                      re.findall(r'href\s*=\s*"[^"/?]*\.oao"', 
    1338                                                 response)) 
    1339                     if len(addOnFiles)==0: 
    1340                         if firstload: 
    1341                             raise RepositoryException("Unable to load reposito"+ 
    1342                                                       "ry data: this is not an"+ 
    1343                                                       " Orange add-on "+ 
    1344                                                       "repository!") 
    1345                         else: 
    1346                             warnings.warn("Repository '%s' is empty ..." % 
    1347                                           self.name, Warning, 0) 
    1348                     self.addons = {} 
    1349                     for addOnFile in addOnFiles: 
    1350                         try: 
    1351                             addOnTmpFile = urllib.urlretrieve(self.url+"/"+addOnFile)[0] 
    1352                             self._add_packed_addon(addOnTmpFile, addOnFile) 
    1353                         except Exception, e: 
    1354                             warnings.warn("Ignoring '%s' in repository '%s' "+ 
    1355                                           "because of an error: %s" % 
    1356                                           (addOnFile, self.name, e), 
    1357                                           Warning, 0) 
    1358                 elif protocol == "file": # A local repository: open each and every archive to obtain data 
    1359                     dir = self.url.replace("file://","") 
    1360                     if not os.path.isdir(dir): 
    1361                         raise RepositoryException("Repository '%s' is not valid: '%s' is not a directory." % (self.name, dir)) 
    1362                     self.addons = {} 
    1363                     for addOnFile in glob.glob(os.path.join(dir, "*.oao")): 
    1364                         try: 
    1365                             self._add_packed_addon(addOnFile, 
    1366                                                   os.path.split(addOnFile)[1]) 
    1367                         except Exception, e: 
    1368                             warnings.warn("Ignoring '%s' in repository '%s' "+ 
    1369                                           "because of an error: %s" % 
    1370                                           (addOnFile, self.name, e), 
    1371                                           Warning, 0) 
    1372                 return True 
    1373             finally: 
    1374                 self._refresh_index() 
    1375         return False 
    1376          
    1377     def _add_to_index(self, addon, text): 
    1378         """ 
    1379         Add the words, found in given text, to the search index, to be 
    1380         associated with given add-on. 
    1381          
    1382         :param addon: add-on to add to the search index. 
    1383         :type addon: :class:`OrangeAddOnInRepo` 
    1384          
    1385         :param text: text from which to extract words to be added to the index. 
    1386         :type text: str 
    1387         """ 
    1388         words = [word for word in re.split(index_re, text.lower()) 
    1389                  if len(word)>1] 
    1390         for word in words: 
    1391             bisect.insort_right(self.index, (word, addon.id) ) 
    1392                  
    1393     def _refresh_index(self): 
    1394         """ 
    1395         Rebuild the search index. 
    1396         """ 
    1397         self.index = [] 
    1398         for addOnVersions in self.addons.values(): 
    1399             for addOn in addOnVersions: 
    1400                 for str in [addOn.name, addOn.description] + addOn.author_creators + addOn.author_contributors + addOn.author_organizations + addOn.tags +\ 
    1401                            [" ".join([w.name, w.contact, w.description, w.category, w.tags]) for w in addOn.widgets]: 
    1402                     self._add_to_index(addOn, str) 
    1403         self.last_search_phrase = None 
    1404         self.last_search_result = None 
    1405                      
    1406     def search_index(self, phrase): 
    1407         """ 
    1408         Search the word index for the given phrase and return a list of 
    1409         matching add-ons' GUIDs. The given phrase is split into sequences 
    1410         of alphanumeric characters, just like strings are split when 
    1411         building the index, and resulting add-ons match all of the words in 
    1412         the phrase. 
    1413          
    1414         :param phrase: a phrase to search. 
    1415         :type phrase: str 
    1416         """ 
    1417         if phrase == self.last_search_phrase: 
    1418             return self.last_search_result 
    1419          
    1420         words = [word for word in re.split(index_re, phrase.lower()) if word!=""] 
    1421         result = set(self.addons.keys()) 
    1422         for word in words: 
    1423             subset = set() 
    1424             i = bisect.bisect_left(self.index, (word, "")) 
    1425             while self.index[i][0][:len(word)] == word: 
    1426                 subset.add(self.index[i][1]) 
    1427                 i += 1 
    1428                 if i>= len(self.index): break 
    1429             result = result.intersection(subset) 
    1430         self.last_search_phrase = phrase 
    1431         self.last_search_result = result 
    1432         return result 
    1433          
    1434 class OrangeDefaultAddOnRepository(OrangeAddOnRepository): 
    1435     """ 
    1436     Repository of Orange add-ons that is added by default. 
    1437      
    1438     It has a hard-coded name of "Default Orange Repository (orange.biolab.si)" 
    1439     and URL "http://orange.biolab.si/add-ons/"; those arguments cannot be 
    1440     passed to the constructor. Also, the :obj:`force` parameter is set to 
    1441     :obj:`True`. Other parameters are passed to the superclass' constructor. 
    1442     """ 
    1443      
    1444     def __init__(self, **args): 
    1445         OrangeAddOnRepository.__init__(self, "Default Orange Repository (orange.biolab.si)", 
    1446                                        "http://orange.biolab.si/add-ons/", 
    1447                                        force=True, **args) 
    1448          
    1449     def clone(self, new=None): 
    1450         if not new: 
    1451             new = OrangeDefaultAddOnRepository(load=False) 
    1452         new.name = self.name 
    1453         new.url = self.url 
    1454         return OrangeAddOnRepository.clone(self, new) 
    1455          
    1456 def load_installed_addons_from_dir(dir): 
    1457     """ 
    1458     Populate the :obj:`installed_addons` dictionary with add-ons, installed 
    1459     into direct subdirectories of the given directory. 
    1460      
    1461     :param dir: directory to search for add-ons. 
    1462     :type dir: str 
    1463     """ 
    1464     if os.path.isdir(dir): 
    1465         for name in os.listdir(dir): 
    1466             addOnDir = os.path.join(dir, name) 
    1467             if not os.path.isdir(addOnDir) or name.startswith("."): 
    1468                 continue 
    1469             try: 
    1470                 addOn = OrangeAddOnInstalled(addOnDir) 
    1471             except Exception, e: 
    1472                 warnings.warn("Add-on in directory '%s' has no valid descriptor (addon.xml): %s" % (addOnDir, e), Warning, 0) 
    1473                 continue 
    1474             if addOn.id in installed_addons: 
    1475                 warnings.warn("Add-on in directory '%s' has the same ID as the addon in '%s'!" % (addOnDir, installed_addons[addOn.id].directory), Warning, 0) 
    1476                 continue 
    1477             installed_addons[addOn.id] = addOn 
    1478  
    1479 def repository_list_filename(): 
    1480     """ 
    1481     Return the full filename of pickled add-on repository list. It resides 
    1482     within Orange settings directory.  
    1483     """ 
    1484     orange_settings_dir = os.path.realpath(Orange.utils.environ.orange_settings_dir) 
    1485     list_file_name = os.path.join(orange_settings_dir, "repositoryList.pickle") 
    1486     if not os.path.isfile(list_file_name): 
    1487         # Try to move the config from the old location. 
    1488         try: 
    1489             canvas_settings_dir = os.path.realpath(Orange.utils.environ.canvas_settings_dir) 
    1490             old_list_file_name = os.path.join(canvas_settings_dir, "repositoryList.pickle") 
    1491             shutil.move(old_list_file_name, list_file_name) 
    1492         except: 
    1493             pass 
    1494      
    1495     return list_file_name 
    1496  
    1497 available_repositories = None 
    1498              
    1499 def load_repositories(refresh=True): 
    1500     """ 
    1501     Populate the :obj:`available_repositories` list by reading the pickled 
    1502     repository list and adding the default repository 
    1503     (http://orange.biolab.si/addons) if it is not yet on the list. Optionally, 
    1504     lists of add-ons in repositories are refreshed. 
    1505      
    1506     :param refresh: determines whether the add-on lists of repositories should 
    1507         be refreshed. 
    1508     :type refresh: boolean 
    1509     """ 
    1510     listFileName = repository_list_filename() 
    1511     global available_repositories 
    1512     available_repositories = [] 
    1513     if os.path.isfile(listFileName): 
    1514         try: 
    1515             import cPickle 
    1516             file = open(listFileName, 'rb') 
    1517             available_repositories = [repo.clone() for repo 
    1518                                       in cPickle.load(file)] 
    1519             file.close() 
    1520         except Exception, e: 
    1521             warnings.warn("Unable to load repository list! Error: %s" % e, Warning, 0) 
    1522     try: 
    1523         update_default_repositories(refresh=refresh) 
    1524     except Exception, e: 
    1525         warnings.warn("Unable to refresh default repositories: %s" % (e), Warning, 0) 
    1526  
    1527     if refresh: 
    1528         for r in available_repositories: 
    1529             #TODO: # Should show some progress (and enable cancellation) 
    1530             try: 
    1531                 r.refreshdata(force=False) 
    1532             except Exception, e: 
    1533                 warnings.warn("Unable to refresh repository %s! Error: %s" % (r.name, e), Warning, 0) 
    1534     save_repositories() 
    1535  
    1536 def save_repositories(): 
    1537     """ 
    1538     Save the add-on repository list (:obj:`available_repositories`) to a  
    1539     specific file (see :obj:`repository_list_filename`). 
    1540     """ 
    1541     listFileName = repository_list_filename() 
    1542     try: 
    1543         import cPickle 
    1544         global available_repositories 
    1545         cPickle.dump(available_repositories, open(listFileName, 'wb')) 
    1546     except Exception, e: 
    1547         warnings.warn("Unable to save repository list! Error: %s" % e, Warning, 0) 
    1548      
    1549  
    1550 def update_default_repositories(refresh=True): 
    1551     """ 
    1552     Make sure the appropriate default repository (and no other 
    1553     :class:`OrangeDefaultAddOnRepository`) is in :obj:`available_repositories`. 
    1554     This function is called by :obj:`load_repositories`. 
    1555      
    1556     :param refresh: determines whether the add-on list of added default 
    1557         repository should be refreshed. 
    1558     :type refresh: boolean 
    1559     """ 
    1560     global available_repositories 
    1561     default = [OrangeDefaultAddOnRepository(load=False)] 
    1562     defaultKeys = [(repo.url, repo.name) for repo in default] 
    1563     existingKeys = [(repo.url, repo.name) for repo in available_repositories] 
    1564      
    1565     for i, key in enumerate(defaultKeys): 
    1566         if key not in existingKeys: 
    1567             available_repositories.append(default[i]) 
    1568             if refresh: 
    1569                 default[i].refreshdata(firstload=True) 
    1570      
    1571     to_remove = [] 
    1572     for i, key in enumerate(existingKeys): 
    1573         if isinstance(available_repositories[i], OrangeDefaultAddOnRepository) and \ 
    1574            key not in defaultKeys: 
    1575             to_remove.append(available_repositories[i]) 
    1576     for tr in to_remove: 
    1577         available_repositories.remove(tr) 
    1578  
    1579 addon_directories = [] 
    1580 def add_addon_directories_to_path(): 
    1581     """ 
    1582     Add directories, related to installed add-ons, to python path, if they are 
    1583     not yet there. Added directories are also stored into 
    1584     :obj:`addon_directories`. If this function is called more than once, the 
    1585     non-first invocation first removes the entries in :obj:`addon_directories` 
    1586     from the path. 
    1587      
    1588     If an add-on is installed in directory D, those directories are added to 
    1589     python path (:obj:`sys.path`): 
    1590     
    1591       - D, 
    1592       - D/widgets 
    1593       - D/widgets/prototypes 
    1594       - D/lib-<platform> 
    1595        
    1596    Here, <platform> is a "-"-separated concatenation of :obj:`sys.platform`, 
    1597    result of :obj:`platform.machine` (an empty string is replaced by "x86") and 
    1598    comma-separated first two components of :obj:`sys.version_info`. 
    1599    """ 
    1600     import os, sys 
    1601     global addon_directories, registered_addons 
    1602     sys.path = [dir for dir in sys.path if dir not in addon_directories] 
    1603     for addOn in installed_addons.values() + registered_addons: 
    1604         path = addOn.directory 
    1605         for p in [os.path.join(path, "widgets", "prototypes"), 
    1606                   os.path.join(path, "widgets"), 
    1607                   path, 
    1608                   os.path.join(path, "lib-%s" % "-".join(( sys.platform, "x86" 
    1609                                                            if (platform.machine()=="") 
    1610                                                            else platform.machine(), 
    1611                                                            ".".join(map(str, sys.version_info[:2])) )) )]: 
    1612             if os.path.isdir(p) and not any([Orange.utils.environ.samepath(p, x) 
    1613                                              for x in sys.path]): 
    1614                 if p not in sys.path: 
    1615                     addon_directories.append(p) 
    1616                     sys.path.insert(0, p) 
    1617  
    1618 def _deltree(dirname): 
    1619      if os.path.exists(dirname): 
    1620         for root,dirs,files in os.walk(dirname): 
    1621                 for dir in dirs: 
    1622                         _deltree(os.path.join(root,dir)) 
    1623                 for file in files: 
    1624                         os.remove(os.path.join(root,file))      
    1625         os.rmdir(dirname) 
    1626  
    1627 class InstallationException(Exception): 
    1628     """ 
    1629     An exception that occurs during add-on installation. Behaves exactly as 
    1630     :class:`Exception`. 
    1631  
    1632     """ 
    1633     pass 
    1634  
    1635 def install_addon(oaofile, global_install=False, refresh=True): 
    1636     """ 
    1637     Install an add-on from given .oao package. Installation means unpacking the 
    1638     .oao file to an appropriate directory (:obj:`Orange.utils.environ.add_ons_dir_user` or 
    1639     :obj:`Orange.utils.environ.add_ons_dir_sys`, depending on the 
    1640     :obj:`global_install` parameter), creating an 
    1641     :class:`OrangeAddOnInstalled` instance and adding this object into the 
    1642     :obj:`installed_addons` dictionary. 
    1643      
    1644     :param global_install: determines whether the given add-on is to be 
    1645         installed globally, ie. for all users. Administrative privileges on 
    1646         the file system are usually needed for that. 
    1647     :type global_install: boolean 
    1648      
    1649     :param refresh: determines whether add-on list change callback 
    1650         functions are to be called after the installation process. This 
    1651         should always be True, except when multiple operations are executed 
    1652         in a batch. 
    1653     :type refresh: boolean 
    1654     """ 
    1655     try: 
    1656         pack = ZipFile(oaofile, 'r') 
    1657     except Exception, e: 
    1658         raise Exception("Unable to unpack the add-on '%s': %s" % (oaofile, e)) 
    1659          
    1660     try: 
    1661         for filename in pack.namelist(): 
    1662             if filename[0]=="\\" or filename[0]=="/" or filename[:2]=="..": 
    1663                 raise InstallationException("Refusing to install unsafe package: it contains file named '%s'!" % filename) 
    1664          
    1665         root = Orange.utils.environ.add_ons_dir if global_install else Orange.utils.environ.add_ons_dir_user 
    1666          
    1667         try: 
    1668             manifest = _zip_open(pack, 'addon.xml') 
    1669             addon = OrangeAddOn(manifest) 
    1670         except Exception, e: 
    1671             raise Exception("Unable to load add-on descriptor: %s" % e) 
    1672          
    1673         if addon.id in installed_addons: 
    1674             raise InstallationException("An add-on with this ID is already installed!") 
    1675          
    1676         # Find appropriate directory name for the new add-on. 
    1677         i = 1 
    1678         while True: 
    1679             addon_dir = os.path.join(root, 
    1680                                      addon.preferred_directory + ("" if i<2 else " (%d)"%i)) 
    1681             if not os.path.exists(addon_dir): 
    1682                 break 
    1683             i += 1 
    1684             if i>1000:  # Avoid infinite loop if something goes wrong. 
    1685                 raise InstallationException("Cannot find an appropriate directory name for the new add-on.") 
    1686          
    1687         # Install (unpack) add-on. 
    1688         try: 
    1689             os.makedirs(addon_dir) 
    1690         except OSError, e: 
    1691             if e.errno==13:  # Permission Denied 
    1692                 raise InstallationException("No write permission for the add-ons directory!") 
    1693         except Exception, e: 
    1694                 raise Exception("Cannot create a new add-on directory: %s" % e) 
    1695  
    1696         try: 
    1697             if hasattr(pack, "extractall"): 
    1698                 pack.extractall(addon_dir) 
    1699             else: # Python 2.5 
    1700                 import shutil 
    1701                 for filename in pack.namelist(): 
    1702                     # don't include leading "/" from file name if present 
    1703                     if filename[0] == '/': 
    1704                         targetpath = os.path.join(addon_dir, filename[1:]) 
    1705                     else: 
    1706                         targetpath = os.path.join(addon_dir, filename) 
    1707                     upperdirs = os.path.dirname(targetpath) 
    1708                     if upperdirs and not os.path.exists(upperdirs): 
    1709                         os.makedirs(upperdirs) 
    1710              
    1711                     if filename[-1] == '/': 
    1712                         if not os.path.isdir(targetpath): 
    1713                             os.mkdir(targetpath) 
    1714                         continue 
    1715              
    1716                     source = _zip_open(pack, filename) 
    1717                     target = file(targetpath, "wb") 
    1718                     shutil.copyfileobj(source, target) 
    1719                     source.close() 
    1720                     target.close() 
    1721  
    1722             addon = OrangeAddOnInstalled(addon_dir) 
    1723             installed_addons[addon.id] = addon 
    1724         except Exception, e: 
    1725             try: 
    1726                 _deltree(addon_dir) 
    1727             except: 
    1728                 pass 
    1729             raise Exception("Cannot install add-on: %s"%e) 
    1730          
    1731         if refresh: 
    1732             refresh_addons() 
     233            raise Exception("Unable to install add-on: %s" % e) 
    1733234    finally: 
    1734         pack.close() 
    1735  
    1736 def install_addon_from_repo(addon_in_repo, global_install=False, refresh=True): 
    1737     """ 
    1738     Retrieve the .oao file from the repository, then call :obj:`install_addon` 
    1739     on the resulting file, passing it given parameters. 
    1740      
    1741     :param addon_in_repo: add-on in repository to be installed. 
    1742     :type addon_in_repo: :class:`OrangeAddOnInRepo` 
    1743     """ 
    1744     try: 
    1745         tmpfile = urllib.urlretrieve(addon_in_repo.repository.url+"/"+addon_in_repo.filename)[0] 
    1746     except Exception, e: 
    1747         raise InstallationException("Unable to download add-on from repository: %s" % e) 
    1748     install_addon(tmpfile, global_install, refresh) 
    1749  
    1750 def load_addons(): 
    1751     """ 
    1752     Call :obj:`load_installed_addons_from_dir` on a system-wide add-on 
    1753     installation directory (:obj:`orngEnviron.addOnsDirSys`) and user-specific 
    1754     add-on installation directory (:obj:`orngEnviron.addOnsDirUser`). 
    1755     """ 
    1756     load_installed_addons_from_dir(Orange.utils.environ.add_ons_dir) 
    1757     load_installed_addons_from_dir(Orange.utils.environ.add_ons_dir_user) 
    1758  
    1759 def refresh_addons(reload_path=False): 
    1760     """ 
    1761     Call add-on list change callbacks (ie. functions in 
    1762     :obj:`addon_refresh_callback`) and, optionally, refresh the python path 
    1763     (:obj:`sys.path`) with appropriate add-on directories (ie. call 
    1764     :obj:`addon_refresh_callback`). 
    1765      
    1766     :param reload_path: determines whether python path should be refreshed. 
    1767     :type reload_path: boolean 
    1768     """ 
    1769     if reload_path: 
    1770         add_addon_directories_to_path() 
     235        shutil.rmtree(tmpdir, ignore_errors=True) 
     236 
     237    for p in list(sys.path): 
     238        site.addsitedir(p) 
     239    reload(pkg_resources) 
     240    for p in list(sys.path): 
     241        pkg_resources.find_distributions(p) 
     242    from orngRegistry import load_new_addons 
     243    load_new_addons() 
     244    load_installed_addons() 
    1771245    for func in addon_refresh_callback: 
    1772246        func() 
    1773          
    1774 # Registered add-ons support         
     247 
     248def uninstall(name, progress_callback=None): 
     249    try: 
     250        import pip.req 
     251        ao = pip.req.InstallRequirement(name, None) 
     252        ao.uninstall(True) 
     253    except ImportError: 
     254        raise Exception("Pip is required for add-on uninstallation. Install pip and try again.") 
     255 
     256def upgrade(name, progress_callback=None): 
     257    install(name, progress_callback) 
     258 
     259load_installed_addons() 
     260 
     261 
     262 
     263# Support for loading legacy "registered" add-ons 
    1775264def __read_addons_list(addons_file, systemwide): 
    1776265    if os.path.isfile(addons_file): 
    1777         name_path_list = [tuple([x.strip() for x in lne.split("\t")]) 
    1778                           for lne in file(addons_file, "rt")] 
    1779         return [OrangeRegisteredAddOn(name, path, systemwide) 
    1780                 for (name, path) in name_path_list] 
     266        return [tuple([x.strip() for x in lne.split("\t")]) 
     267                for lne in file(addons_file, "rt")] 
    1781268    else: 
    1782269        return [] 
    1783      
    1784 def __read_addon_lists(user_only=False): 
    1785     return __read_addons_list(os.path.join(Orange.utils.environ.orange_settings_dir, "add-ons.txt"), 
    1786                               False) + ([] if user_only else 
    1787                                         __read_addons_list(os.path.join(Orange.utils.environ.install_dir, "add-ons.txt"), 
    1788                                                            True)) 
    1789  
    1790 def __write_addon_lists(addons, user_only=False): 
    1791     file(os.path.join(Orange.utils.environ.orange_settings_dir, "add-ons.txt"), "wt").write("\n".join(["%s\t%s" % (a.name, a.directory) for a in addons if not a.systemwide])) 
    1792     if not user_only: 
    1793         file(os.path.join(Orange.utils.environ.install_dir        , "add-ons.txt"), "wt").write("\n".join(["%s\t%s" % (a.name, a.directory) for a in addons if     a.systemwide])) 
    1794  
    1795 def register_addon(name, path, add = True, refresh=True, systemwide=False): 
    1796     """ 
    1797     Register the given path as an registered add-on with a given descriptive 
    1798     name. The operation is persistent, ie. on next :obj:`load_addons` call the 
    1799     path will still appear as registered. 
    1800      
    1801     :param name: a descriptive name for the registered add-on. 
    1802     :type name: str 
    1803      
    1804     :param path: path to be registered. 
    1805     :type path: str 
    1806      
    1807     :param add: if False, the given path is UNREGISTERED instead of registered. 
    1808     :type add: boolean 
    1809      
    1810     :param refresh: determines whether callbacks should be called after the 
    1811         procedure. 
    1812     :type refresh: boolean 
    1813      
    1814     :param systemwide: determines whether the path is to be registered 
    1815         system-wide, i.e. for all users. Administrative privileges on the 
    1816         filesystem are usually needed for that. 
    1817     :type systemwide: boolean 
    1818     """ 
    1819     if not add: 
    1820         unregister_addon(name, path, user_only=not systemwide) 
    1821     else: 
    1822         if os.path.isfile(path): 
    1823             path = os.path.dirname(path) 
    1824         __write_addon_lists([a for a in __read_addon_lists(user_only=not systemwide) 
    1825                              if a.name != name and a.directory != path] +\ 
    1826                            ([OrangeRegisteredAddOn(name, path, systemwide)] or []), 
    1827                              user_only=not systemwide) 
    1828      
    1829         global registered_addons 
    1830         registered_addons.append( OrangeRegisteredAddOn(name, path, systemwide) ) 
    1831     if refresh: 
    1832         refresh_addons() 
    1833  
    1834 def unregister_addon(name, path, user_only=False): 
    1835     """ 
    1836     Unregister the given path if it has been registered as an add-on with given 
    1837     descriptive name. The operation is persistent, ie. on next 
    1838     :obj:`load_addons` call the path will no longer appear as registered. 
    1839      
    1840     :param name: a descriptive name of the registered add-on to be unregistered. 
    1841     :type name: str 
    1842      
    1843     :param path: path to be unregistered. 
    1844     :type path: str 
    1845  
    1846     :param user_only: determines whether the path to be unregistered is 
    1847         registered for this user only, ie. not system-wide. Administrative 
    1848         privileges on the filesystem are usually needed to unregister a 
    1849         system-wide registered add-on. 
    1850     :type systemwide: boolean 
    1851     """ 
    1852     global registered_addons 
    1853     registered_addons = [ao for ao in registered_addons 
    1854                          if (ao.name!=name) or (ao.directory!=path) or 
    1855                          (user_only and ao.systemwide)] 
    1856     __write_addon_lists([a for a in __read_addon_lists(user_only=user_only) 
    1857                          if a.name != name and a.directory != path], 
    1858                          user_only=user_only) 
    1859  
    1860  
    1861 def __get_registered_addons(): 
    1862     return {'registered_addons': __read_addon_lists()} 
    1863  
    1864 load_addons() 
    1865 globals().update(__get_registered_addons()) 
    1866  
    1867 addon_refresh_callback = [] 
    1868 globals().update({'addon_refresh_callback': addon_refresh_callback}) 
    1869  
    1870 add_addon_directories_to_path() 
    1871  
    1872 load_repositories(refresh=False) 
     270 
     271registered = __read_addons_list(os.path.join(Orange.utils.environ.orange_settings_dir, "add-ons.txt"), False) + \ 
     272             __read_addons_list(os.path.join(Orange.utils.environ.install_dir, "add-ons.txt"), True) 
     273 
     274for name, path in registered: 
     275    for p in [os.path.join(path, "widgets", "prototypes"), 
     276          os.path.join(path, "widgets"), 
     277          path, 
     278          os.path.join(path, "lib-%s" % "-".join(( sys.platform, "x86" if (platform.machine()=="") 
     279          else platform.machine(), ".".join(map(str, sys.version_info[:2])) )) )]: 
     280        if os.path.isdir(p) and not any([Orange.utils.environ.samepath(p, x) 
     281                                         for x in sys.path]): 
     282            if p not in sys.path: 
     283                sys.path.insert(0, p) 
     284 
     285#TODO Show some progress to the user at least during the installation procedure. 
  • Orange/utils/environ.py

    r10838 r11010  
    5959`dataset_install_dir`: 
    6060    Directory with example data sets. 
    61  
    62 `network_install_dir`: 
    63     Directory with example networks. 
    6461 
    6562`add_ons_dir`: 
     
    131128    doc_install_dir = os.path.join(install_dir, "doc") 
    132129    dataset_install_dir = os.path.join(install_dir, "doc", "datasets") 
    133     network_install_dir = os.path.join(install_dir, "doc", "networks") 
    134130 
    135131    canvas_install_dir = os.path.join(install_dir, "OrangeCanvas") 
     
    180176                    "widget_install_dir", "icons_install_dir", 
    181177                    "doc_install_dir", "dataset_install_dir",  
    182                     "network_install_dir", "add_ons_dir", "add_ons_dir_user", 
     178                    "add_ons_dir", "add_ons_dir_user", 
    183179                    "application_dir", "output_dir", "default_reports_dir", 
    184180                    "orange_settings_dir", "canvas_settings_dir", 
  • docs/development/rst/conf.py

    r10881 r11028  
    1212# serve to show the default. 
    1313 
    14 import sys, os 
     14import os, sys 
    1515 
    16 import imp 
    17 module_setup = imp.load_source('module_setup', os.path.join(os.path.dirname(__file__), '..', '..', '..', 'setup.py')) 
    18 VERSION = module_setup.VERSION 
     16sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) 
    1917 
    20 # If extensions (or modules to document with autodoc) are in another directory, 
    21 # add these directories to sys.path here. If the directory is relative to the 
    22 # documentation root, use os.path.abspath to make it absolute, like shown here. 
    23 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../'))) 
    24 import Orange 
     18from conf import * 
    2519 
    26 # -- General configuration ----------------------------------------------------- 
     20TITLE = "%s v%s" % ("Orange Library Development", VERSION) 
    2721 
    28 # Add any Sphinx extension module names here, as strings. They can be extensions 
    29 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 
    30 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath'] 
     22html_title = TITLE 
     23epub_title = TITLE 
    3124 
    32 # Add any paths that contain templates here, relative to this directory. 
    33 templates_path = ['_templates'] 
    34  
    35 # The suffix of source filenames. 
    36 source_suffix = '.rst' 
    37  
    38 # The encoding of source files. 
    39 #source_encoding = 'utf-8' 
    40  
    41 # The master toctree document. 
    42 master_doc = 'index' 
    43  
    44 # General information about the project. 
    45 project = u'Orange' 
    46 copyright = u'Bioinformatics Laboratory, FRI UL' 
    47  
    48 # The version info for the project you're documenting, acts as replacement for 
    49 # |version| and |release|, also used in various other places throughout the 
    50 # built documents. 
    51 # 
    52 # The short X.Y version. 
    53 version = VERSION 
    54 # The full version, including alpha/beta/rc tags. 
    55 release = VERSION 
    56  
    57 # The language for content autogenerated by Sphinx. Refer to documentation 
    58 # for a list of supported languages. 
    59 #language = None 
    60  
    61 # There are two options for replacing |today|: either, you set today to some 
    62 # non-false value, then it is used: 
    63 #today = '' 
    64 # Else, today_fmt is used as the format for a strftime call. 
    65 #today_fmt = '%B %d, %Y' 
    66  
    67 # List of documents that shouldn't be included in the build. 
    68 #unused_docs = [] 
    69  
    70 # List of directories, relative to source directory, that shouldn't be searched 
    71 # for source files. 
    72 exclude_trees = ['_build'] 
    73  
    74 # The reST default role (used for this markup: `text`) to use for all documents. 
    75 #default_role = None 
    76  
    77 # If true, '()' will be appended to :func: etc. cross-reference text. 
    78 #add_function_parentheses = True 
    79  
    80 # If true, the current module name will be prepended to all description 
    81 # unit titles (such as .. function::). 
    82 #add_module_names = True 
    83  
    84 # If true, sectionauthor and moduleauthor directives will be shown in the 
    85 # output. They are ignored by default. 
    86 #show_authors = False 
    87  
    88 # The name of the Pygments (syntax highlighting) style to use. 
    89 pygments_style = 'sphinx' 
    90  
    91 # A list of ignored prefixes for module index sorting. 
    92 #modindex_common_prefix = [] 
    93  
    94  
    95 # -- Options for HTML output --------------------------------------------------- 
    96  
    97 # The theme to use for HTML and HTML Help pages.  Major themes that come with 
    98 # Sphinx are currently 'default' and 'sphinxdoc'. 
    99 html_theme = 'orange_theme' 
    100  
    101 # Theme options are theme-specific and customize the look and feel of a theme 
    102 # further.  For a list of options available for each theme, see the 
    103 # documentation. 
    104 html_theme_options = {"collapsiblesidebar": "false"} 
    105  
    106 if html_theme == "orange_theme": 
    107     html_theme_options.update({"orangeheaderfooter": "false"}) 
    108  
    109 # Add any paths that contain custom themes here, relative to this directory. 
    110 html_theme_path = ["../../sphinx-ext/themes"] 
    111  
    112 # The name for this set of Sphinx documents.  If None, it defaults to 
    113 # "<project> v<release> documentation". 
    114 html_title = "Orange Library Development" 
    115  
    116 # A shorter title for the navigation bar.  Default is the same as html_title. 
    117 #html_short_title = None 
    118  
    119 # The name of an image file (relative to this directory) to place at the top 
    120 # of the sidebar. 
    121 #html_logo = None 
    122  
    123 # The name of an image file (within the static path) to use as favicon of the 
    124 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 
    125 # pixels large. 
    126 #html_favicon = None 
    127  
    128 # Add any paths that contain custom static files (such as style sheets) here, 
    129 # relative to this directory. They are copied after the builtin static files, 
    130 # so a file named "default.css" will overwrite the builtin "default.css". 
    131 html_static_path = [] 
    132  
    133 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 
    134 # using the given strftime format. 
    135 #html_last_updated_fmt = '%b %d, %Y' 
    136  
    137 # If true, SmartyPants will be used to convert quotes and dashes to 
    138 # typographically correct entities. 
    139 #html_use_smartypants = True 
    140  
    141 # Custom sidebar templates, maps document names to template names. 
    142 #html_sidebars = {} 
    143  
    144 # Additional templates that should be rendered to pages, maps page names to 
    145 # template names. 
    146 #html_additional_pages = {} 
    147  
    148 # If false, no module index is generated. 
    149 #html_use_modindex = True 
    150  
    151 # If false, no index is generated. 
    152 #html_use_index = True 
    153  
    154 # If true, the index is split into individual pages for each letter. 
    155 #html_split_index = False 
    156  
    157 # If true, links to the reST sources are added to the pages. 
    158 #html_show_sourcelink = True 
    159  
    160 # If true, an OpenSearch description file will be output, and all pages will 
    161 # contain a <link> tag referring to it.  The value of this option must be the 
    162 # base URL from which the finished HTML is served. 
    163 #html_use_opensearch = '' 
    164  
    165 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 
    166 #html_file_suffix = '' 
    167  
    168 # Output file base name for HTML help builder. 
    169 htmlhelp_basename = 'developmentsdoc' 
    170  
    171  
    172 # -- Options for LaTeX output -------------------------------------------------- 
    173  
    174 # The paper size ('letter' or 'a4'). 
    175 #latex_paper_size = 'letter' 
    176  
    177 # The font size ('10pt', '11pt' or '12pt'). 
    178 #latex_font_size = '10pt' 
    179  
    180 # Grouping the document tree into LaTeX files. List of tuples 
    181 # (source start file, target name, title, author, documentclass [howto/manual]). 
    18225latex_documents = [ 
    183   ('index', 'development.tex', u'Orange Library Development', 
    184    u'Biolab', 'manual'), 
    185 ] 
    186  
    187 # The name of an image file (relative to this directory) to place at the top of 
    188 # the title page. 
    189 #latex_logo = None 
    190  
    191 # For "manual" documents, if this is true, then toplevel headings are parts, 
    192 # not chapters. 
    193 #latex_use_parts = False 
    194  
    195 # Additional stuff for the LaTeX preamble. 
    196 #latex_preamble = '' 
    197  
    198 # Documents to append as an appendix to all manuals. 
    199 #latex_appendices = [] 
    200  
    201 # If false, no module index is generated. 
    202 #latex_use_modindex = True 
    203  
    204 # -- Options for Epub output --------------------------------------------------- 
    205  
    206 # Bibliographic Dublin Core info. 
    207 epub_title = u'Orange Library Development' 
    208 epub_author = u'Biolab' 
    209 epub_publisher = u'Biolab' 
    210 epub_copyright = u'Bioinformatics Laboratory, FRI UL' 
    211  
    212 # The language of the text. It defaults to the language option 
    213 # or en if the language is not set. 
    214 epub_language = 'en' 
    215  
    216 # The scheme of the identifier. Typical schemes are ISBN or URL. 
    217 #epub_scheme = '' 
    218  
    219 # The unique identifier of the text. This can be a ISBN number 
    220 # or the project homepage. 
    221 #epub_identifier = '' 
    222  
    223 # A unique identification for the text. 
    224 #epub_uid = '' 
    225  
    226 # HTML files that should be inserted before the pages created by sphinx. 
    227 # The format is a list of tuples containing the path and title. 
    228 #epub_pre_files = [] 
    229  
    230 # HTML files that should be inserted after the pages created by sphinx. 
    231 # The format is a list of tuples containing the path and title. 
    232 #epub_post_files = [] 
    233  
    234 # A list of files that should not be packed into the epub file. 
    235 epub_exclude_files = ["index.html", "genindex.html", "py-modindex.html", "search.html"] 
    236  
    237 # The depth of the table of contents in toc.ncx. 
    238 #epub_tocdepth = 3 
    239  
    240 # Allow duplicate toc entries. 
    241 #epub_tocdup = True 
    242  
    243 # Example configuration for intersphinx: refer to the Python standard library. 
    244 intersphinx_mapping = { 
    245     'python': ('http://docs.python.org/', None), 
    246     'reference': ('http://orange.biolab.si/doc/reference/', 'http://orange.biolab.si/doc/reference/_objects/'), 
    247 } 
     26    ('index', 'reference.tex', TITLE, 
     27     AUTHOR, 'manual'), 
     28    ] 
  • docs/extend-widgets/rst/conf.py

    r10881 r11028  
    1212# serve to show the default. 
    1313 
    14 import sys, os 
     14import os, sys 
    1515 
    16 import imp 
    17 module_setup = imp.load_source('module_setup', os.path.join(os.path.dirname(__file__), '..', '..', '..', 'setup.py')) 
    18 VERSION = module_setup.VERSION 
     16sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) 
    1917 
    20 # If extensions (or modules to document with autodoc) are in another directory, 
    21 # add these directories to sys.path here. If the directory is relative to the 
    22 # documentation root, use os.path.abspath to make it absolute, like shown here. 
    23 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../'))) 
    24 import Orange 
     18from conf import * 
    2519 
    26 # -- General configuration ----------------------------------------------------- 
     20TITLE = "%s v%s" % ("Orange Widget Development", VERSION) 
    2721 
    28 # Add any Sphinx extension module names here, as strings. They can be extensions 
    29 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 
    30 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath'] 
     22html_title = TITLE 
     23epub_title = TITLE 
    3124 
    32 # Add any paths that contain templates here, relative to this directory. 
    33 templates_path = ['_templates'] 
     25latex_documents = [ 
     26    ('index', 'reference.tex', TITLE, 
     27     AUTHOR, 'manual'), 
     28    ] 
    3429 
    35 # The suffix of source filenames. 
    36 source_suffix = '.rst' 
    37  
    38 # The encoding of source files. 
    39 #source_encoding = 'utf-8' 
    40  
    41 # The master toctree document. 
    42 master_doc = 'index' 
    43  
    44 # General information about the project. 
    45 project = u'Orange' 
    46 copyright = u'Bioinformatics Laboratory, FRI UL' 
    47  
    48 # The version info for the project you're documenting, acts as replacement for 
    49 # |version| and |release|, also used in various other places throughout the 
    50 # built documents. 
    51 # 
    52 # The short X.Y version. 
    53 version = VERSION 
    54 # The full version, including alpha/beta/rc tags. 
    55 release = VERSION 
    56  
    57 # The language for content autogenerated by Sphinx. Refer to documentation 
    58 # for a list of supported languages. 
    59 #language = None 
    60  
    61 # There are two options for replacing |today|: either, you set today to some 
    62 # non-false value, then it is used: 
    63 #today = '' 
    64 # Else, today_fmt is used as the format for a strftime call. 
    65 #today_fmt = '%B %d, %Y' 
    66  
    67 # List of documents that shouldn't be included in the build. 
    68 #unused_docs = [] 
    69  
    70 # List of directories, relative to source directory, that shouldn't be searched 
    71 # for source files. 
    72 exclude_trees = ['_build'] 
    73  
    74 # The reST default role (used for this markup: `text`) to use for all documents. 
    75 #default_role = None 
    76  
    77 # If true, '()' will be appended to :func: etc. cross-reference text. 
    78 #add_function_parentheses = True 
    79  
    80 # If true, the current module name will be prepended to all description 
    81 # unit titles (such as .. function::). 
    82 #add_module_names = True 
    83  
    84 # If true, sectionauthor and moduleauthor directives will be shown in the 
    85 # output. They are ignored by default. 
    86 #show_authors = False 
    87  
    88 # The name of the Pygments (syntax highlighting) style to use. 
    89 pygments_style = 'sphinx' 
    90  
    91 # A list of ignored prefixes for module index sorting. 
    92 #modindex_common_prefix = [] 
    93  
    94  
    95 # -- Options for HTML output --------------------------------------------------- 
    96  
    97 # The theme to use for HTML and HTML Help pages.  Major themes that come with 
    98 # Sphinx are currently 'default' and 'sphinxdoc'. 
    99 html_theme = 'orange_theme' 
    100  
    101 # Theme options are theme-specific and customize the look and feel of a theme 
    102 # further.  For a list of options available for each theme, see the 
    103 # documentation. 
    104 html_theme_options = {"collapsiblesidebar": "false"} 
    105  
    106 if html_theme == "orange_theme": 
    107     html_theme_options.update({"orangeheaderfooter": "false"}) 
    108  
    109 # Add any paths that contain custom themes here, relative to this directory. 
    110 html_theme_path = ["../../sphinx-ext/themes"] 
    111  
    112 # The name for this set of Sphinx documents.  If None, it defaults to 
    113 # "<project> v<release> documentation". 
    114 html_title = "Orange Widgets Development" 
    115  
    116 # A shorter title for the navigation bar.  Default is the same as html_title. 
    117 #html_short_title = None 
    118  
    119 # The name of an image file (relative to this directory) to place at the top 
    120 # of the sidebar. 
    121 #html_logo = None 
    122  
    123 # The name of an image file (within the static path) to use as favicon of the 
    124 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 
    125 # pixels large. 
    126 #html_favicon = None 
    127  
    128 # Add any paths that contain custom static files (such as style sheets) here, 
    129 # relative to this directory. They are copied after the builtin static files, 
    130 # so a file named "default.css" will overwrite the builtin "default.css". 
    131 html_static_path = [] 
    132  
    133 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 
    134 # using the given strftime format. 
    135 #html_last_updated_fmt = '%b %d, %Y' 
    136  
    137 # If true, SmartyPants will be used to convert quotes and dashes to 
    138 # typographically correct entities. 
    139 #html_use_smartypants = True 
    140  
    141 # Custom sidebar templates, maps document names to template names. 
    142 #html_sidebars = {} 
    143  
    144 # Additional templates that should be rendered to pages, maps page names to 
    145 # template names. 
    146 #html_additional_pages = {} 
    147  
    148 # If false, no module index is generated. 
    149 #html_use_modindex = True 
    150  
    151 # If false, no index is generated. 
    152 #html_use_index = True 
    153  
    154 # If true, the index is split into individual pages for each letter. 
    155 #html_split_index = False 
    156  
    157 # If true, links to the reST sources are added to the pages. 
    158 #html_show_sourcelink = True 
    159  
    160 # If true, an OpenSearch description file will be output, and all pages will 
    161 # contain a <link> tag referring to it.  The value of this option must be the 
    162 # base URL from which the finished HTML is served. 
    163 #html_use_opensearch = '' 
    164  
    165 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 
    166 #html_file_suffix = '' 
    167  
    168 # Output file base name for HTML help builder. 
    169 htmlhelp_basename = 'extendwidgetsdoc' 
    170  
    171  
    172 # -- Options for LaTeX output -------------------------------------------------- 
    173  
    174 # The paper size ('letter' or 'a4'). 
    175 #latex_paper_size = 'letter' 
    176  
    177 # The font size ('10pt', '11pt' or '12pt'). 
    178 #latex_font_size = '10pt' 
    179  
    180 # Grouping the document tree into LaTeX files. List of tuples 
    181 # (source start file, target name, title, author, documentclass [howto/manual]). 
    182 latex_documents = [ 
    183   ('index', 'extend-widgets.tex', u'Orange Widgets Development', 
    184    u'Biolab', 'manual'), 
    185 ] 
    186  
    187 # The name of an image file (relative to this directory) to place at the top of 
    188 # the title page. 
    189 #latex_logo = None 
    190  
    191 # For "manual" documents, if this is true, then toplevel headings are parts, 
    192 # not chapters. 
    193 #latex_use_parts = False 
    194  
    195 # Additional stuff for the LaTeX preamble. 
    196 #latex_preamble = '' 
    197  
    198 # Documents to append as an appendix to all manuals. 
    199 #latex_appendices = [] 
    200  
    201 # If false, no module index is generated. 
    202 #latex_use_modindex = True 
    203  
    204 # -- Options for Epub output --------------------------------------------------- 
    205  
    206 # Bibliographic Dublin Core info. 
    207 epub_title = u'Orange Widgets Development' 
    208 epub_author = u'Biolab' 
    209 epub_publisher = u'Biolab' 
    210 epub_copyright = u'Bioinformatics Laboratory, FRI UL' 
    211  
    212 # The language of the text. It defaults to the language option 
    213 # or en if the language is not set. 
    214 epub_language = 'en' 
    215  
    216 # The scheme of the identifier. Typical schemes are ISBN or URL. 
    217 #epub_scheme = '' 
    218  
    219 # The unique identifier of the text. This can be a ISBN number 
    220 # or the project homepage. 
    221 #epub_identifier = '' 
    222  
    223 # A unique identification for the text. 
    224 #epub_uid = '' 
    225  
    226 # HTML files that should be inserted before the pages created by sphinx. 
    227 # The format is a list of tuples containing the path and title. 
    228 #epub_pre_files = [] 
    229  
    230 # HTML files that should be inserted after the pages created by sphinx. 
    231 # The format is a list of tuples containing the path and title. 
    232 #epub_post_files = [] 
    233  
    234 # A list of files that should not be packed into the epub file. 
    235 epub_exclude_files = ["index.html", "genindex.html", "py-modindex.html", "search.html"] 
    236  
    237 # The depth of the table of contents in toc.ncx. 
    238 #epub_tocdepth = 3 
    239  
    240 # Allow duplicate toc entries. 
    241 #epub_tocdup = True 
    242  
    243 # Example configuration for intersphinx: refer to the Python standard library. 
    244 intersphinx_mapping = { 
    245     'python': ('http://docs.python.org/', None), 
    246     'reference': ('http://orange.biolab.si/doc/reference/', 'http://orange.biolab.si/doc/reference/_objects/'), 
    247 } 
  • docs/first-steps/rst/conf.py

    r10881 r11028  
    1212# serve to show the default. 
    1313 
    14 import sys, os 
     14import os, sys 
    1515 
    16 import imp 
    17 module_setup = imp.load_source('module_setup', os.path.join(os.path.dirname(__file__), '..', '..', '..', 'setup.py')) 
    18 VERSION = module_setup.VERSION 
     16sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) 
    1917 
    20 # If extensions (or modules to document with autodoc) are in another directory, 
    21 # add these directories to sys.path here. If the directory is relative to the 
    22 # documentation root, use os.path.abspath to make it absolute, like shown here. 
    23 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../'))) 
    24 import Orange 
     18from conf import * 
    2519 
    26 # -- General configuration ----------------------------------------------------- 
     20TITLE = "%s v%s" % ("First Steps in Orange Canvas", VERSION) 
    2721 
    28 # Add any Sphinx extension module names here, as strings. They can be extensions 
    29 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 
    30 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath'] 
     22html_title = TITLE 
     23epub_title = TITLE 
    3124 
    32 # Add any paths that contain templates here, relative to this directory. 
    33 templates_path = ['_templates'] 
     25latex_documents = [ 
     26    ('index', 'reference.tex', TITLE, 
     27     AUTHOR, 'manual'), 
     28    ] 
    3429 
    35 # The suffix of source filenames. 
    36 source_suffix = '.rst' 
    37  
    38 # The encoding of source files. 
    39 #source_encoding = 'utf-8' 
    40  
    41 # The master toctree document. 
    42 master_doc = 'index' 
    43  
    44 # General information about the project. 
    45 project = u'Orange' 
    46 copyright = u'Bioinformatics Laboratory, FRI UL' 
    47  
    48 # The version info for the project you're documenting, acts as replacement for 
    49 # |version| and |release|, also used in various other places throughout the 
    50 # built documents. 
    51 # 
    52 # The short X.Y version. 
    53 version = VERSION 
    54 # The full version, including alpha/beta/rc tags. 
    55 release = VERSION 
    56  
    57 # The language for content autogenerated by Sphinx. Refer to documentation 
    58 # for a list of supported languages. 
    59 #language = None 
    60  
    61 # There are two options for replacing |today|: either, you set today to some 
    62 # non-false value, then it is used: 
    63 #today = '' 
    64 # Else, today_fmt is used as the format for a strftime call. 
    65 #today_fmt = '%B %d, %Y' 
    66  
    67 # List of documents that shouldn't be included in the build. 
    68 #unused_docs = [] 
    69  
    70 # List of directories, relative to source directory, that shouldn't be searched 
    71 # for source files. 
    72 exclude_trees = ['_build'] 
    73  
    74 # The reST default role (used for this markup: `text`) to use for all documents. 
    75 #default_role = None 
    76  
    77 # If true, '()' will be appended to :func: etc. cross-reference text. 
    78 #add_function_parentheses = True 
    79  
    80 # If true, the current module name will be prepended to all description 
    81 # unit titles (such as .. function::). 
    82 #add_module_names = True 
    83  
    84 # If true, sectionauthor and moduleauthor directives will be shown in the 
    85 # output. They are ignored by default. 
    86 #show_authors = False 
    87  
    88 # The name of the Pygments (syntax highlighting) style to use. 
    89 pygments_style = 'sphinx' 
    90  
    91 # A list of ignored prefixes for module index sorting. 
    92 #modindex_common_prefix = [] 
    93  
    94  
    95 # -- Options for HTML output --------------------------------------------------- 
    96  
    97 # The theme to use for HTML and HTML Help pages.  Major themes that come with 
    98 # Sphinx are currently 'default' and 'sphinxdoc'. 
    99 html_theme = 'orange_theme' 
    100  
    101 # Theme options are theme-specific and customize the look and feel of a theme 
    102 # further.  For a list of options available for each theme, see the 
    103 # documentation. 
    104 html_theme_options = {"collapsiblesidebar": "false"} 
    105  
    106 if html_theme == "orange_theme": 
    107     html_theme_options.update({"orangeheaderfooter": "false"}) 
    108  
    109 # Add any paths that contain custom themes here, relative to this directory. 
    110 html_theme_path = ["../../sphinx-ext/themes"] 
    111  
    112 # The name for this set of Sphinx documents.  If None, it defaults to 
    113 # "<project> v<release> documentation". 
    114 html_title = "First Steps in Orange Canvas" 
    115  
    116 # A shorter title for the navigation bar.  Default is the same as html_title. 
    117 #html_short_title = None 
    118  
    119 # The name of an image file (relative to this directory) to place at the top 
    120 # of the sidebar. 
    121 #html_logo = None 
    122  
    123 # The name of an image file (within the static path) to use as favicon of the 
    124 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 
    125 # pixels large. 
    126 #html_favicon = None 
    127  
    128 # Add any paths that contain custom static files (such as style sheets) here, 
    129 # relative to this directory. They are copied after the builtin static files, 
    130 # so a file named "default.css" will overwrite the builtin "default.css". 
    131 html_static_path = [] 
    132  
    133 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 
    134 # using the given strftime format. 
    135 #html_last_updated_fmt = '%b %d, %Y' 
    136  
    137 # If true, SmartyPants will be used to convert quotes and dashes to 
    138 # typographically correct entities. 
    139 #html_use_smartypants = True 
    140  
    141 # Custom sidebar templates, maps document names to template names. 
    142 #html_sidebars = {} 
    143  
    144 # Additional templates that should be rendered to pages, maps page names to 
    145 # template names. 
    146 #html_additional_pages = {} 
    147  
    148 # If false, no module index is generated. 
    149 #html_use_modindex = True 
    150  
    151 # If false, no index is generated. 
    152 #html_use_index = True 
    153  
    154 # If true, the index is split into individual pages for each letter. 
    155 #html_split_index = False 
    156  
    157 # If true, links to the reST sources are added to the pages. 
    158 #html_show_sourcelink = True 
    159  
    160 # If true, an OpenSearch description file will be output, and all pages will 
    161 # contain a <link> tag referring to it.  The value of this option must be the 
    162 # base URL from which the finished HTML is served. 
    163 #html_use_opensearch = '' 
    164  
    165 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 
    166 #html_file_suffix = '' 
    167  
    168 # Output file base name for HTML help builder. 
    169 htmlhelp_basename = 'firststepsdoc' 
    170  
    171  
    172 # -- Options for LaTeX output -------------------------------------------------- 
    173  
    174 # The paper size ('letter' or 'a4'). 
    175 #latex_paper_size = 'letter' 
    176  
    177 # The font size ('10pt', '11pt' or '12pt'). 
    178 #latex_font_size = '10pt' 
    179  
    180 # Grouping the document tree into LaTeX files. List of tuples 
    181 # (source start file, target name, title, author, documentclass [howto/manual]). 
    182 latex_documents = [ 
    183   ('index', 'first-steps.tex', u'First Steps', 
    184    u'Biolab', 'manual'), 
    185 ] 
    186  
    187 # The name of an image file (relative to this directory) to place at the top of 
    188 # the title page. 
    189 #latex_logo = None 
    190  
    191 # For "manual" documents, if this is true, then toplevel headings are parts, 
    192 # not chapters. 
    193 #latex_use_parts = False 
    194  
    195 # Additional stuff for the LaTeX preamble. 
    196 #latex_preamble = '' 
    197  
    198 # Documents to append as an appendix to all manuals. 
    199 #latex_appendices = [] 
    200  
    201 # If false, no module index is generated. 
    202 #latex_use_modindex = True 
    203  
    204 # -- Options for Epub output --------------------------------------------------- 
    205  
    206 # Bibliographic Dublin Core info. 
    207 epub_title = u'First Steps in Orange Canvas' 
    208 epub_author = u'Biolab' 
    209 epub_publisher = u'Biolab' 
    210 epub_copyright = u'Bioinformatics Laboratory, FRI UL' 
    211  
    212 # The language of the text. It defaults to the language option 
    213 # or en if the language is not set. 
    214 epub_language = 'en' 
    215  
    216 # The scheme of the identifier. Typical schemes are ISBN or URL. 
    217 #epub_scheme = '' 
    218  
    219 # The unique identifier of the text. This can be a ISBN number 
    220 # or the project homepage. 
    221 #epub_identifier = '' 
    222  
    223 # A unique identification for the text. 
    224 #epub_uid = '' 
    225  
    226 # HTML files that should be inserted before the pages created by sphinx. 
    227 # The format is a list of tuples containing the path and title. 
    228 #epub_pre_files = [] 
    229  
    230 # HTML files that should be inserted after the pages created by sphinx. 
    231 # The format is a list of tuples containing the path and title. 
    232 #epub_post_files = [] 
    233  
    234 # A list of files that should not be packed into the epub file. 
    235 epub_exclude_files = ["index.html", "genindex.html", "py-modindex.html", "search.html"] 
    236  
    237 # The depth of the table of contents in toc.ncx. 
    238 #epub_tocdepth = 3 
    239  
    240 # Allow duplicate toc entries. 
    241 #epub_tocdup = True 
    242  
    243 # Example configuration for intersphinx: refer to the Python standard library. 
    244 intersphinx_mapping = { 
    245     'python': ('http://docs.python.org/', None), 
    246     'reference': ('http://orange.biolab.si/doc/reference/', 'http://orange.biolab.si/doc/reference/_objects/'), 
    247 } 
  • docs/reference/rst/Orange.classification.tree.rst

    r10959 r11028  
    13691369=================== 
    13701370 
    1371 .. include:: /SimpleTreeLearner.txt 
     1371.. include:: SimpleTreeLearner.txt 
    13721372 
    13731373--------         
  • docs/reference/rst/Orange.data.formats.rst

    r9928 r11028  
    11.. py:currentmodule:: Orange.data 
     2 
     3.. _Orange-data-formats: 
    24 
    35======================= 
  • docs/reference/rst/Orange.data.table.rst

    r10962 r11028  
    99(:obj:`Orange.data.Domain`). 
    1010 
    11 Data tables are usually loaded from a file (see :doc:`Orange.data.formats`):: 
     11Data tables are usually loaded from a file (see :ref:`Orange-data-formats`):: 
    1212 
    1313    import Orange 
     
    7676        Read data from the given file. If the name includes the 
    7777        extension it must be one of the known file formats 
    78         (see :doc:`/Orange.data.formats`). If no extension is given, the 
     78        (see :ref:`Orange-data-formats`). If no extension is given, the 
    7979        directory is searched for any file with recognized extensions. If the 
    8080        file is not found, Orange will also search the directories 
  • docs/reference/rst/conf.py

    r10881 r11028  
    1212# serve to show the default. 
    1313 
    14 import sys, os 
     14import os, sys 
    1515 
    16 #rewrite formatargs function with different defaults 
    17 sys.path.insert(0, os.path.dirname(__file__)) 
    18 import myinspect 
    19 import sphinx.ext.autodoc 
    20 import numpydoc 
    21 sphinx.ext.autodoc.inspect = myinspect 
    22 numpydoc.docscrape.inspect = myinspect 
     16sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) 
    2317 
    24 import imp 
    25 module_setup = imp.load_source('module_setup', os.path.join(os.path.dirname(__file__), '..', '..', '..', 'setup.py')) 
    26 VERSION = module_setup.VERSION 
     18from conf import * 
    2719 
    28 #disable deprecation decorators for the documentation 
    29 os.environ["orange_no_deprecated_members"] = "1" 
     20TITLE = "%s v%s" % ("Orange Scripting Reference", VERSION) 
    3021 
    31 # If extensions (or modules to document with autodoc) are in another directory, 
    32 # add these directories to sys.path here. If the directory is relative to the 
    33 # documentation root, use os.path.abspath to make it absolute, like shown here. 
    34 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../'))) 
    35 import Orange 
     22html_title = TITLE 
     23epub_title = TITLE 
    3624 
    37 # -- General configuration ----------------------------------------------------- 
    38  
    39 # Add any Sphinx extension module names here, as strings. They can be extensions 
    40 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 
    41 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'numpydoc'] 
    42  
    43  
    44 # Numpydoc generates autosummary directives for all undocumented members. Orange documentation only includes documented 
    45 # member, so _str_member_list is modified to return [] where a list of undocumented members is originally returned. 
    46 numpydoc.docscrape_sphinx.SphinxDocString._str_member_list # if numpydoc changes, this line will cause an error 
    47 numpydoc.docscrape_sphinx.SphinxDocString._str_member_list = lambda x, y : [] 
    48  
    49  
    50 # Add any paths that contain templates here, relative to this directory. 
    51 templates_path = ['_templates'] 
    52  
    53 # The suffix of source filenames. 
    54 source_suffix = '.rst' 
    55  
    56 # The encoding of source files. 
    57 #source_encoding = 'utf-8' 
    58  
    59 # The master toctree document. 
    60 master_doc = 'index' 
    61  
    62 # General information about the project. 
    63 project = u'Orange' 
    64 copyright = u'Bioinformatics Laboratory, FRI UL' 
    65  
    66 # The version info for the project you're documenting, acts as replacement for 
    67 # |version| and |release|, also used in various other places throughout the