Commits

Martin Frlin committed eb33307 Merge

Merged biolab/orange into default

Comments (0)

Files changed (30)

 source/orangeom/lib_vectors_auto.txt
 
 # Ignore files created by setup.py.
+_build
 build
 dist
 MANIFEST
 69c197e609e8d9d6a247661aa4a9a928efb960fd 2.6a2
 84d3895ad2a55acb7e69173e22314a602c87a29f 2.6
 8adaabe1a15bdc88d3baa633042c02c1a7bc7fa9 2.6.1
+19b97395b702366db35cc58615e3d016ce1c9701 2.7
 exclude source/orangeom/lib_vectors.cpp
 exclude source/orangene/lib_vectors.cpp
 
-recursive-include docs *.rst *.py *.png *.css *.txt Makefile
+recursive-include docs *.rst *.py *.png *.gif *.css *.txt *.tab *.basket *.arff *.csv *.res Makefile
+prune docs/build
 
 graft docs/sphinx-ext
 graft distribute

Orange/OrangeCanvas/application/canvasmain.py

         self.get_started_action = \
             QAction(self.tr("Get Started"), self,
                     objectName="get-started-action",
-                    toolTip=self.tr("View a 'Getting Started' video."),
+                    toolTip=self.tr("View a 'Get Started' introduction."),
                     triggered=self.get_started,
                     icon=canvas_icons("Get Started.svg")
                     )

Orange/OrangeCanvas/canvas/scene.py

 
         return items[0] if items else None
 
-    if PYQT_VERSION_STR < "4.9":
+    if map(int, PYQT_VERSION_STR.split('.')) < [4, 9]:
         # For QGraphicsObject subclasses items, itemAt ... return a
         # QGraphicsItem wrapper instance and not the actual class instance.
         def itemAt(self, *args, **kwargs):

Orange/OrangeCanvas/scheme/widgetsscheme.py

   :bases:
 
 """
+import sys
 import logging
 
 import sip
             try:
                 handler(*args)
             except Exception:
-                log.exception("Error")
+                sys.excepthook(*sys.exc_info())
+                log.exception("Error calling '%s' of '%s'",
+                              handler.__name__, node.title)
             finally:
                 app.restoreOverrideCursor()
 
         try:
             widget.handleNewSignals()
         except Exception:
-            log.exception("Error")
+            sys.excepthook(*sys.exc_info())
+            log.exception("Error calling 'handleNewSignals()' of '%s'",
+                          node.title)
         finally:
             app.restoreOverrideCursor()
 

Orange/OrangeWidgets/Classify/OWNeuralNetwork.py

+"""
+<name>Neural Network</name>
+<description>Neural network learner.</description>
+<priority>20<priority>
+<icon>icons/NeuralNetwork.svg</icon>
+
+"""
+
+import Orange
+from orngWrap import PreprocessedLearner
+
+from OWWidget import *
+import OWGUI
+
+class OWNeuralNetwork(OWWidget):
+    settingsList = ["name", "n_mid", "reg_fact", "max_iter", "normalize"]
+
+    def __init__(self, parent=None, signalManager=None,
+                 title="Neural Network"):
+        OWWidget.__init__(self, parent, signalManager, title,
+                          wantMainArea=False)
+
+        self.inputs = [("Data", Orange.data.Table, self.set_data),
+                       ("Preprocess", PreprocessedLearner,
+                        self.set_preprocessor)
+                       ]
+        self.outputs = [("Learner", Orange.classification.Learner),
+                        ("Classifier", Orange.classification.Classifier)
+                        ]
+
+        self.name = "Neural Network"
+        self.n_mid = 20
+        self.reg_fact = 1
+        self.max_iter = 300
+        self.normalize = True
+
+        self.loadSettings()
+
+        box = OWGUI.widgetBox(self.controlArea, "Name", addSpace=True)
+        OWGUI.lineEdit(box, self, "name")
+
+        box = OWGUI.widgetBox(self.controlArea, "Settings", addSpace=True)
+        OWGUI.spin(box, self, "n_mid", 2, 10000, 1,
+                   label="Hidden layer neurons",
+                   tooltip="Number of neurons in the hidden layer."
+                   )
+
+        OWGUI.doubleSpin(box, self, "reg_fact", 0.1, 10.0, 0.1,
+                         label="Regularization factor",
+                         )
+
+        OWGUI.spin(box, self, "max_iter", 100, 10000, 1,
+                   label="Max iterations",
+                   tooltip="Maximal number of optimization iterations."
+                   )
+
+        OWGUI.button(self.controlArea, self, "&Apply",
+                     callback=self.apply,
+                     tooltip="Create the learner and apply it on input data.",
+                     autoDefault=True
+                     )
+
+        OWGUI.checkBox(box, self, 'normalize', 'Normalize the data')
+
+        self.data = None
+        self.preprocessor = None
+        self.apply()
+
+    def set_data(self, data=None):
+        self.data = data
+        self.error([0])
+        self.data = data
+        self.apply()
+
+    def set_preprocessor(self, preprocessor=None):
+        self.preprocessor = preprocessor
+
+    def apply(self):
+        learner = Orange.classification.neural.NeuralNetworkLearner(
+            name=self.name, n_mid=self.n_mid,
+            reg_fact=self.reg_fact, max_iter=self.max_iter
+        )
+
+        if self.preprocessor is not None:
+            learner = self.preprocessor.wrapLearner(learner)
+
+        classifier = None
+        self.error([1])
+        if self.data is not None:
+            try:
+                classifier = learner(self.data)
+                classifier.name = self.name
+            except Exception, ex:
+                self.error(1, str(ex))
+
+        self.send("Learner", learner)
+        self.send("Classifier", classifier)
+
+    def sendReport(self):
+        self.reportSettings("Parameters",
+                            [("Hidden layer neurons", self.n_mid),
+                             ("Regularization factor", self.reg_fact),
+                             ("Max iterations", self.max_iter)]
+                            )
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    w = OWNeuralNetwork()
+    w.show()
+    app.exec_()
+    w.saveSettings()

Orange/OrangeWidgets/Data/OWDataTable.py

 <contact>Peter Juvan (peter.juvan@fri.uni-lj.si)</contact>
 """
 
+from xml.sax.saxutils import escape
+import Orange
+
 from OWWidget import *
 import OWGUI
 import math
 from orngDataCaching import *
 import OWColorPalette
 
-
 NAME = "Data Table"
 
 DESCRIPTION = "Shows data in a spreadsheet."
 OUTPUTS = [("Selected Data", ExampleTable, Default),
            ("Other Data", ExampleTable)]
 
-##############################################################################
 
-def safe_call(func):
-    from functools import wraps
-    @wraps(func)
-    def wrapper(*args, **kwargs):
-        try:
-            return func(*args, **kwargs)
-        except Exception, ex:
-            print >> sys.stderr, func.__name__, "call error", ex 
-            return QVariant()
-    return wrapper
-    
+def header_text(feature, labels=None):
+    """
+    Return an header text for an `Orange.feature.Descriptor` instance
+    `feature`. If labels is not none it should be a sequence of keys into
+    `feature.attributes` to include in the header (one per line). If the
+    `feature.attribures` does not contain a value for the key the returned
+    text will include an empty line for it.
+
+    """
+    lines = [feature.name]
+    if labels is not None:
+        lines += [str(feature.attributes.get(label, ""))
+                  for label in labels]
+    return "\n".join(lines)
+
+
+def header_tooltip(feature, labels=None):
+    """
+    Return an header tooltip text for an `Orange.feature.Decriptor` instance.
+    """
+
+    if labels is None:
+        labels = feature.attributes.keys()
+
+    pairs = [(escape(key), escape(str(feature.attributes[key])))
+             for key in labels if key in feature.attributes]
+    tip = "<b>%s</b>" % escape(feature.name)
+    tip = "<br/>".join([tip] + ["%s = %s" % pair for pair in pairs])
+    return tip
+
 
 class ExampleTableModel(QAbstractItemModel):
+    Attribute, ClassVar, ClassVars, Meta = range(4)
+
     def __init__(self, examples, dist, *args):
         QAbstractItemModel.__init__(self, *args)
         self.examples = examples
+        self.domain = examples.domain
         self.dist = dist
-        self.attributes = list(self.examples.domain.attributes)
-        self.class_var = self.examples.domain.classVar
+        self.attributes = list(examples.domain.attributes)
+        self.class_var = self.examples.domain.class_var
+        self.class_vars = list(self.examples.domain.class_vars)
         self.metas = self.examples.domain.getmetas().values()
-        self.all_attrs = self.attributes + ([self.class_var] if self.class_var else []) + self.metas
-        self.cls_color = QColor(160,160,160)
-        self.meta_color = QColor(220,220,200)
+        # Attributes/features for all table columns
+        self.all_attrs = (self.attributes +
+                          ([self.class_var] if self.class_var else []) +
+                          self.class_vars + self.metas)
+        # Table roles for all table columns
+        self.table_roles = \
+            (([ExampleTableModel.Attribute] * len(self.attributes)) +
+             ([ExampleTableModel.ClassVar] if self.class_var else []) +
+             ([ExampleTableModel.ClassVars] * len(self.class_vars)) +
+             ([ExampleTableModel.Meta] * len(self.metas)))
+
+        # True if an feature at column i is continuous
+        self._continuous_mask = [isinstance(attr, Orange.feature.Continuous)
+                                 for attr in self.all_attrs]
+
+        self._meta_mask = [role == ExampleTableModel.Meta
+                           for role in self.table_roles]
+
+        self.cls_color = QColor(160, 160, 160)
+        self.meta_color = QColor(220, 220, 200)
+
+        role_to_color = {ExampleTableModel.Attribute: None,
+                         ExampleTableModel.ClassVar: self.cls_color,
+                         ExampleTableModel.ClassVars: self.cls_color,
+                         ExampleTableModel.Meta: self.meta_color}
+
+        self.background_colors = map(role_to_color.get, self.table_roles)
+
+        # all attribute labels (annotation) keys
+        self.attr_labels = sorted(
+            reduce(set.union,
+                   [attr.attributes for attr in self.all_attrs],
+                   set())
+        )
+
+        # text for all header items (no attr labels by default)
+        self.header_labels = [header_text(feature)
+                              for feature in self.all_attrs]
+
         self.sorted_map = range(len(self.examples))
-        
-        self.attr_labels = sorted(reduce(set.union, [attr.attributes for attr in self.all_attrs], set()))
+
         self._show_attr_labels = False
         self._other_data = {}
-        
+
     def get_show_attr_labels(self):
         return self._show_attr_labels
-    
+
     def set_show_attr_labels(self, val):
-        self.emit(SIGNAL("layoutAboutToBeChanged()"))
-        self._show_attr_labels = val
-        self.emit(SIGNAL("headerDataChanged(Qt::Orientation, int, int)"),
-                  Qt.Horizontal, 
-                  0, 
-                  len(self.all_attrs) - 1
-                  )
-        self.emit(SIGNAL("layoutChanged()"))
-        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), 
-                  self.index(0,0),
-                  self.index(len(self.examples) - 1, len(self.all_attrs) - 1)
-                  )
-        
-    show_attr_labels = pyqtProperty("bool", 
-                                   fget=get_show_attr_labels,
-                                   fset=set_show_attr_labels,
-                                   )
-    
-    @safe_call
-    def data(self, index, role):
+        if self._show_attr_labels != val:
+            self.emit(SIGNAL("layoutAboutToBeChanged()"))
+            self._show_attr_labels = val
+            if val:
+                labels = self.attr_labels
+            else:
+                labels = None
+            self.header_labels = [header_text(feature, labels)
+                                  for feature in self.all_attrs]
+
+            self.emit(SIGNAL("headerDataChanged(Qt::Orientation, int, int)"),
+                      Qt.Horizontal,
+                      0,
+                      len(self.all_attrs) - 1)
+
+            self.emit(SIGNAL("layoutChanged()"))
+
+            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
+                      self.index(0, 0),
+                      self.index(len(self.examples) - 1,
+                                 len(self.all_attrs) - 1)
+                      )
+
+    show_attr_labels = pyqtProperty("bool",
+                                    fget=get_show_attr_labels,
+                                    fset=set_show_attr_labels)
+
+    def data(self, index, role,
+             # For optimizing out LOAD_GLOBAL byte code instructions in
+             # the item role tests (goes from 14 us to 9 us average).
+             _str=str,
+             _Qt_DisplayRole=Qt.DisplayRole,
+             _Qt_BackgroundRole=Qt.BackgroundRole,
+             _OWGUI_TableBarItem_BarRole=OWGUI.TableBarItem.BarRole,
+             _OWGUI_TableValueRole=OWGUI.TableValueRole,
+             _OWGUI_TableClassValueRole=OWGUI.TableClassValueRole,
+             _OWGUI_TableVariable=OWGUI.TableVariable,
+             # Some cached local precomputed values.
+             # All of the above roles we respond to
+             _recognizedRoles=set([Qt.DisplayRole,
+                                   Qt.BackgroundRole,
+                                   OWGUI.TableBarItem.BarRole,
+                                   OWGUI.TableValueRole,
+                                   OWGUI.TableClassValueRole,
+                                   OWGUI.TableVariable]),
+             ):
+        """
+        Return the data for `role` for an value at `index`.
+        """
+        if role not in _recognizedRoles:
+            return self._other_data.get((index.row(), index.column(), role),
+                                        None)
+
         row, col = self.sorted_map[index.row()], index.column()
         example, attr = self.examples[row], self.all_attrs[col]
+
         val = example[attr]
-        domain = self.examples.domain
-        if role == Qt.DisplayRole:
-                return QVariant(str(val))
-        elif role == Qt.BackgroundRole:
-            #check if attr is actual class or a duplication in the meta attributes
-            if attr == self.class_var and col == len(domain.attributes) and domain.classVar:
-                return QVariant(self.cls_color)
-            elif attr in self.metas:
-                return QVariant(self.meta_color)
-        elif role == OWGUI.TableBarItem.BarRole and val.varType == orange.VarTypes.Continuous \
-                    and not val.isSpecial() and attr not in self.metas:
+
+        if role == _Qt_DisplayRole:
+            return _str(val)
+        elif role == _Qt_BackgroundRole:
+            return self.background_colors[col]
+        elif role == _OWGUI_TableBarItem_BarRole and \
+                self._continuous_mask[col] and not val.isSpecial() and \
+                col < len(self.dist):
             dist = self.dist[col]
-            return QVariant((float(val) - dist.min) / (dist.max - dist.min or 1))
-        elif role == OWGUI.TableValueRole:
-            # The actual value
-            return QVariant(val)
-        elif role == OWGUI.TableClassValueRole: 
+            return (float(val) - dist.min) / (dist.max - dist.min or 1)
+        elif role == _OWGUI_TableValueRole:
+            # The actual value instance (should it be EditRole?)
+            return val
+        elif role == _OWGUI_TableClassValueRole and self.class_var is not None:
             # The class value for the row's example
-            return QVariant(example.get_class())
-        elif role == OWGUI.TableVariable: 
+            return example.get_class()
+        elif role == _OWGUI_TableVariable:
             # The variable descriptor for column
-            return QVariant(val.variable)
-        
-        return self._other_data.get((index.row(), index.column(), role), QVariant())
-        
+            return attr
+
+        return None
+
     def setData(self, index, variant, role):
         self._other_data[index.row(), index.column(), role] = variant
         self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
         if parent.isValid():
             return 0
         else:
-            return max([len(self.examples)] + [row for row, _, _ in self._other_data.keys()])
-        
-    def columnCount(self, index=QModelIndex()):
-        return max([len(self.all_attrs)] + [col for _, col, _ in self._other_data.keys()])
-    
-    @safe_call
+            return len(self.examples)
+
+    def columnCount(self, parent=QModelIndex()):
+        if parent.isValid():
+            return 0
+        else:
+            return len(self.all_attrs)
+
     def headerData(self, section, orientation, role):
         if orientation == Qt.Horizontal:
             attr = self.all_attrs[section]
-            if role ==Qt.DisplayRole:
-                values = [attr.name] + ([str(attr.attributes.get(label, "")) for label in self.attr_labels] if self.show_attr_labels else [])
-                return QVariant("\n".join(values))
+            if role == Qt.DisplayRole:
+                return self.header_labels[section]
             if role == Qt.ToolTipRole:
-                pairs = [(key, str(attr.attributes[key])) for key in self.attr_labels if key in attr.attributes]
-                tip = "<b>%s</b>" % attr.name
-                tip = "<br>".join([tip] + ["%s = %s" % pair for pair in pairs])
-                return QVariant(tip)  
+                return header_tooltip(attr, self.attr_labels)
         else:
             if role == Qt.DisplayRole:
                 return QVariant(section + 1)
         return QVariant()
-    
+
     def sort(self, column, order=Qt.AscendingOrder):
         self.emit(SIGNAL("layoutAboutToBeChanged()"))
         attr = self.all_attrs[column] 
             self.colorSettings = dlg.getColorSchemas()
             self.selectedSchemaIndex = dlg.selectedSchemaIndex
             self.discPalette = dlg.getDiscretePalette("discPalette")
-            self.distColorRgb = dlg.getColor("Default")
+            self.distColor = color = dlg.getColor("Default")
+            self.distColorRgb = (color.red(), color.green(), color.red())
+
+            if self.showDistributions:
+                # Update the views
+                self.cbShowDistributions()
 
     def increaseColWidth(self):
         table = self.tabs.currentWidget()
             self.send("Other Data", None)
             
         self.selectionChangedFlag = False
-            
-        
 
-if __name__=="__main__":
+
+def test():
     a = QApplication(sys.argv)
     ow = OWDataTable()
 
-    #d1 = orange.ExampleTable(r'..\..\doc\datasets\auto-mpg')
-    #d2 = orange.ExampleTable('test-labels')
-    #d3 = orange.ExampleTable(r'..\..\doc\datasets\sponge.tab')
-    #d4 = orange.ExampleTable(r'..\..\doc\datasets\wpbc.csv')
-    d5 = orange.ExampleTable('../../doc/datasets/adult_sample.tab')
-    #d5 = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\wine.tab")
-#    d5 = orange.ExampleTable("adult_sample")
-#    d5 = orange.ExampleTable("/home/marko/tdw")
-    #d5 = orange.ExampleTable(r"e:\Development\Orange Datasets\Cancer\SRBCT.tab")
+    d1 = orange.ExampleTable("auto-mpg")
+    d2 = orange.ExampleTable("sponge.tab")
+    d3 = orange.ExampleTable("wpbc.csv")
+    d4 = orange.ExampleTable("adult_sample.tab")
+    d5 = orange.ExampleTable("wine.tab")
+
     ow.show()
-    #ow.dataset(d1,"auto-mpg")
-    #ow.dataset(d2,"voting")
-    #ow.dataset(d4,"wpbc")
-    ow.dataset(d5,"adult_sample")
+    ow.dataset(d1, "auto-mpg")
+    ow.dataset(d2, "sponge")
+    ow.dataset(d3, "wpbc")
+    ow.dataset(d4, "adult_sample")
+    ow.dataset(d5, "wine")
     a.exec_()
     ow.saveSettings()
+
+
+if __name__ == "__main__":
+    test()

Orange/OrangeWidgets/Data/OWPaintData.py

             return
         # if we start updating from previously undone actions, we cut off redos in our history
         if not self.historyCounter == len(self.dataHistory)-1:
-            self.dataHistory = self.dataHistory[0:self.historyCounter+1]
+            self.dataHistory = self.dataHistory[:self.historyCounter+1]
         # append an update of labels and data
         labels = list(self.classValuesModel)
         self.dataHistory.append((copy.deepcopy(self.graph.data), labels))
                 self.addNewClassLabel()
             # if not, update data
             else:
-                self.graph.data = data
+                self.graph.data = copy.deepcopy(data)
                 self.graph.updateGraph()
             self.updateHistoryBool = True
 
             elif len(self.classValuesModel) < len(labels):
                 self.addNewClassLabel()
             else:
-                self.graph.data = data
+                self.graph.data = copy.deepcopy(data)
                 self.graph.updateGraph()
             self.updateHistoryBool = True
 

Orange/OrangeWidgets/Evaluate/OWTestLearners.py

 import time
 import warnings
 from orngWrap import PreprocessedLearner
-warnings.filterwarnings("ignore", "'id' is not a builtin attribute",
-                        orange.AttributeWarning)
 
 import Orange
 
                 learner = self.preprocessor.wrapLearner(learner)
             try:
                 predictor = learner(new)
+            except Exception, ex:
+                learner_exceptions.append((l, ex))
+                l.scores = []
+                l.results = None
+            else:
                 if (multilabel and isinstance(learner, Orange.multilabel.MultiLabelLearner)) or predictor(new[0]).varType == new.domain.classVar.varType:
                     learners.append(learner)
                     used_ids.append(l.id)
                     l.scores = []
                     l.results = None
 
-            except Exception, ex:
-                learner_exceptions.append((l, ex))
-                l.scores = []
-                l.results = None
-
         if learner_exceptions:
             text = "\n".join("Learner %s ends with exception: %s" % (l.name, str(ex)) \
                              for l, ex in learner_exceptions)
         self.paintscores()
         
     def clearScores(self, ids=None):
+        """
+        Clear/invalidate evaluation results/scores for learners for an
+        `ids` sequence (keys into self.learners). If `ids` is None then
+        invalidate data for all learners.
+
+        """
         if ids is None:
             ids = self.learners.keys()
 
 
     # handle input signals
     def setData(self, data):
-        """handle input train data set"""
+        """
+        Set the input train data set.
+        """
         self.closeContext()
-        
-        multilabel= self.ismultilabel(data)
+
+        self.clearScores()
+
+        multilabel = self.ismultilabel(data)
         if not multilabel:
-            self.data = self.isDataWithClass(data, checkMissing=True) and data or None
+            if self.isDataWithClass(data, checkMissing=True):
+                self.data = orange.Filter_hasClassValue(data)
+            else:
+                self.data = None
         else:
             self.data = data
-        
+
         self.fillClassCombo()
-        if not self.data:
-            # data was removed, remove the scores
-            self.clearScores()
-            self.send("Evaluation Results", None)
-        else:
-            # new data has arrived
-            self.clearScores()
 
-            if not multilabel:
-                self.data = orange.Filter_hasClassValue(self.data)
+        if self.data is not None:
+            # Ensure the correct statistics selection is visible.
+            if self.ismultilabel():
+                statwidget = self.mbox
+                stat = self.mStatistics
+            elif self.isclassification():
+                statwidget = self.cbox
+                stat = self.cStatistics
+            else:
+                statwidget = self.rbox
+                stat = self.rStatistics
 
-            self.statLayout.setCurrentWidget([self.rbox, self.cbox, self.mbox][2 if self.ismultilabel() else self.isclassification()])
+            self.statLayout.setCurrentWidget(statwidget)
 
-            self.stat = [self.rStatistics, self.cStatistics, self.mStatistics][2 if self.ismultilabel() else self.isclassification()]
-
-            if self.learners:
-                self.recompute()
-            
-        self.openContext("", data)
-        self.paintscores()
+            self.stat = stat
 
     def setTestData(self, data):
-        """handle test data set"""
+        """
+        Set the 'Separate Test Data' input.
+        """
         if data is None:
             self.testdata = None
         else:
         self.testDataBtn.setEnabled(self.testdata is not None)
         if self.testdata is not None:
             if self.resampling == 4:
-                if self.data:
-                    self.recompute()
-                else:
-                    for l in self.learners.values():
-                        l.scores = []
-                self.paintscores()
+                self.clearScores()
+
         elif self.resampling == 4 and self.data:
             # test data removed, switch to testing on train data
             self.resampling = 3
-            self.recompute()
+            self.clearScores()
 
     def fillClassCombo(self):
         """upon arrival of new data appropriately set the target class combo"""
             self.targetClass=0
 
     def setLearner(self, learner, id=None):
-        """add/remove a learner"""
-        if learner: # a new or updated learner
-            if id in self.learners: # updated learner
+        """
+        Set the input learner with `id`.
+        """
+        if learner is not None:
+            # a new or updated learner
+            if id in self.learners:
                 time = self.learners[id].time
                 self.learners[id] = Learner(learner, id)
                 self.learners[id].time = time
-            else: # new learner
+                self.learners[id] = self.learners[id]
+                self.clearScores([id])
+            else:
                 self.learners[id] = Learner(learner, id)
-            if self.applyOnAnyChange:
-                self.score([id])
-            else:
-                self.recompute()
-        else: # remove a learner and corresponding results
+        else:
+            # remove a learner and corresponding results
             if id in self.learners:
                 res = self.learners[id].results
                 if res and res.numberOfLearners > 1:
+                    # Remove the learner from the shared results instance
                     old_learner = self.learners[id].learner
                     indx = res.learners.index(old_learner)
                     res.remove(indx)
                     del res.learners[indx]
                 del self.learners[id]
-            self.sendResults()
-        self.paintscores()
-        
+
     def setPreprocessor(self, pp):
         self.preprocessor = pp
-        if self.learners:
-            self.recompute()
+        self.clearScores()
+
+    def handleNewSignals(self):
+        """
+        Update the evaluations/scores after new inputs were received.
+        """
+        def needsupdate(learner_id):
+            return not (self.learners[learner_id].scores or
+                        self.learners[learner_id].results)
+
+        if self.applyOnAnyChange:
+            self.score(filter(needsupdate, self.learners))
+            self.applyBtn.setEnabled(False)
+        else:
+            self.applyBtn.setEnabled(True)
+
+        self.paintscores()
+
+        if self.data is not None:
+            self.openContext("", self.data)
+        else:
+            self.send("Evaluation Results", None)
 
     # handle output signals
 
         if not self.applyOnAnyChange:
             self.applyBtn.setDisabled(self.applyOnAnyChange)
         else:
+            self.clearScores()
             if self.learners:
                 self.recompute()
 
             self.recompute(False)
 
     def applyChange(self):
+        """
+        A change of 'Apply on any change' check box.
+        """
+        def needsupdate(learner_id):
+            return not (self.learners[learner_id].scores or
+                        self.learners[learner_id].results)
+
         if self.applyOnAnyChange:
             self.applyBtn.setDisabled(True)
-        
+            pending = filter(needsupdate, self.learners)
+            if pending:
+                self.score(pending)
+                self.paintscores()
+
     def changedTarget(self):
         self.recomputeCM()
 

Orange/OrangeWidgets/OWGUI.py

 class TableBarItem(QItemDelegate):
     BarRole = OrangeUserRole.next()
     ColorRole = OrangeUserRole.next()
-    def __init__(self, widget, table = None, color = QColor(255, 170, 127), color_schema=None):
+
+    def __init__(self, parent, table=None, color=QColor(255, 170, 127),
+                 color_schema=None):
         """
-        :param widget: OWWidget instance
-        :type widget: :class:`OWWidget.OWWidget
-        :param table: Table
+        :param parent: OWWidget instance
+        :type parent: :class:`PyQt4.QtCore.QObject`
+        :param table: Table (unused, here for backwards compatibility).
         :type table: :class:`Orange.data.Table`
         :param color: Color of the distribution bar.
         :type color: :class:`PyQt4.QtCore.QColor`
         :param color_schema: If not None it must be an instance of
             :class:`OWColorPalette.ColorPaletteGenerator` (note: this
-            parameter, if set, overrides the ``color``)
+            parameter, if set, overrides the ``color``).
         :type color_schema: :class:`OWColorPalette.ColorPaletteGenerator`
-          
+
         """
-        QItemDelegate.__init__(self, widget)
+        QItemDelegate.__init__(self, parent)
         self.color = color
         self.color_schema = color_schema
-        self.widget = widget
         self.table = table
 
     def paint(self, painter, option, index):
         painter.save()
         self.drawBackground(painter, option, index)
-        if self.table is None:
-            table = getattr(index.model(), "examples", None)
-        else:
-            table = self.table
-        ratio = None
+
         ratio, ok = index.data(TableBarItem.BarRole).toDouble()
-        if ratio != ratio: # NaN
+        if not ok or ratio > 1.0 or ratio < 0.0 or ratio != ratio:
+            # not a float, out of 0..1 range or a NaN.
             ratio = None
-        if not ok:
-            ratio = None
-            value, ok = index.data(Qt.DisplayRole).toDouble()
-            if ok and getattr(self.widget, "showBars", False) and table is not None:
-                col = index.column()
-                if col < len(table.normalizers):
-                    max, span = table.normalizers[col]
-                    ratio = (max - value) / span
 
         color = self.color
-        if self.color_schema is not None and table is not None \
-            and table.domain.classVar \
-            and isinstance(table.domain.classVar, orange.EnumVariable):
+        if self.color_schema is not None:
             class_ = index.data(TableClassValueRole).toPyObject()
-            if not class_.isSpecial():
+            if isinstance(class_, orange.Value) and \
+                    isinstance(class_.variable, orange.EnumVariable) and \
+                    not class_.isSpecial():
                 color = self.color_schema[int(class_)]
-        else:
-            color = self.color
 
         if ratio is not None:
-#            painter.save()
-#            pen = QPen(QBrush(color), 3, Qt.SolidLine, Qt.FlatCap, Qt.BevelJoin)
-#            painter.setPen(pen)
-#            painter.drawRect(option.rect.adjusted(0, 3, -option.rect.width() * ratio, -3))
-#            painter.restore()
-
-#            painter.fillRect(option.rect.adjusted(0, 3, -option.rect.width() * ratio, -3), color)
-
             painter.save()
             painter.setPen(QPen(QBrush(color), 5, Qt.SolidLine, Qt.RoundCap))
             rect = option.rect.adjusted(3, 0, -3, -5)
 
         self.drawDisplay(painter, option, text_rect, text)
         painter.restore()
-        
+
+
 class BarItemDelegate(QStyledItemDelegate):
     def __init__(self, parent, brush=QBrush(QColor(255, 170, 127)), scale=(0.0, 1.0)):
         QStyledItemDelegate.__init__(self, parent) 
Add a comment to this file

Orange/OrangeWidgets/icons/NeuralNetwork.svg

Added
New image
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
+<path fill="#989898" d="M27.54,21.505l5.748-6.716l0.696,1.213l1.912-3.712l-4.171-0.222l1.033,1.8l-5.944,6.947
+	C27.087,21.014,27.327,21.247,27.54,21.505z"/>
+<path fill="#989898" d="M33.377,33.354l-7.256-5.682c-0.38,0.173-0.789,0.272-1.213,0.32l7.893,6.181l-1.098,1.559l4.174,0.166
+	l-1.562-3.875L33.377,33.354z"/>
+<path fill="#666666" d="M28.438,23.722c-0.065-0.828-0.386-1.594-0.897-2.217c-0.213-0.258-0.453-0.491-0.726-0.69
+	c-0.472-0.344-1.016-0.601-1.628-0.713c-2.172-0.398-4.256,1.039-4.653,3.211c-0.399,2.176,1.041,4.261,3.212,4.656
+	c0.395,0.072,0.783,0.065,1.163,0.023c0.424-0.048,0.833-0.147,1.213-0.32c1.149-0.524,2.035-1.579,2.279-2.917
+	c0.002-0.011,0.001-0.021,0.003-0.033C28.462,24.383,28.464,24.05,28.438,23.722z"/>
+<path fill="#333333" d="M16.916,35.077l-6.387-2.496c0.426-0.512,0.744-1.124,0.872-1.826c0.15-0.821,0.036-1.628-0.279-2.338
+	l3.197-1.014l3.962,4.189l-1.01,1.247l4.153,0.445l-1.3-3.969l-1.209,1.492l-3.539-3.741l1.739-0.552l0.458,1.728l2.811-3.091
+	l-3.971-1.292l0.447,1.688l-2.247,0.712l-2.189-2.315l2.819-3.094l2.076,0.657l-0.478,1.806l3.971-1.292l-2.811-3.091l-0.426,1.61
+	l-1.59-0.503l2.736-3.003l1.141,1.408l1.299-3.969l-4.153,0.445l1.077,1.329l-3.15,3.457l-3.522-1.115
+	c0.174-1.242-0.242-2.434-1.047-3.29l6.487-2.428l0.279,1.821l3.123-2.772l-3.811-1.711l0.253,1.652l-7.255,2.715
+	c-0.383-0.219-0.799-0.392-1.257-0.476c-2.173-0.398-4.257,1.039-4.653,3.211c-0.399,2.176,1.041,4.261,3.212,4.656
+	c0.775,0.143,1.535,0.042,2.214-0.232l2.101,2.221l-2.146,2.355c-0.232-0.091-0.474-0.164-0.728-0.211
+	c-2.173-0.398-4.257,1.039-4.653,3.211c-0.399,2.176,1.041,4.261,3.212,4.656c1.079,0.198,2.134-0.059,2.974-0.631l6.947,2.716
+	l-0.437,1.708l3.961-1.328l-2.837-3.064L16.916,35.077z M9.856,21.229c0.567-0.424,1.027-0.991,1.307-1.67l3.032,0.96l-2.458,2.698
+	L9.856,21.229z M11.75,24.687l1.806,1.909l-2.984,0.947c-0.22-0.274-0.473-0.521-0.76-0.73L11.75,24.687z"/>
+<g>
+	<path fill="#989898" d="M39.745,27.969c2.173,0.399,4.259-1.04,4.655-3.214c0.396-2.173-1.043-4.256-3.214-4.653
+		c-2.173-0.398-4.257,1.039-4.653,3.211C36.134,25.488,37.574,27.573,39.745,27.969z"/>
+</g>
+<g>
+	<path fill="#989898" d="M39.812,15.969c2.173,0.399,4.259-1.04,4.655-3.214c0.396-2.173-1.043-4.256-3.214-4.653
+		c-2.173-0.398-4.257,1.039-4.653,3.211C36.2,13.488,37.641,15.573,39.812,15.969z"/>
+</g>
+<g>
+	<path fill="#989898" d="M39.812,39.969c2.173,0.399,4.259-1.04,4.655-3.214c0.396-2.173-1.043-4.256-3.214-4.653
+		c-2.173-0.398-4.257,1.039-4.653,3.211C36.2,37.488,37.641,39.573,39.812,39.969z"/>
+</g>
+<path fill="#666666" d="M32.628,38.258l-4.854,0.019c0.303-0.446,0.523-0.957,0.626-1.521c0.171-0.936-0.015-1.845-0.436-2.619
+	l6.171-4.706l0.7,0.958l-1.037,0.904l4.013,1.155l-0.593-4.135l-1.054,0.918l-0.187-0.3l0.734-1.919l-1.859,0.107l-2.396-3.857
+	l1.182-1.649l3.15-0.09l-1.245-2.568l0.319-0.445l1.432,1.193l0.503-4.146L33.812,16.8l1.281,1.067l-0.045,0.063l-0.081-0.167
+	l-0.819,1.352l-5.911-5.792c0.063-0.185,0.128-0.37,0.164-0.568c0.167-0.916-0.007-1.808-0.41-2.572l4.481-0.017l-0.308,1.201
+	l3.961-1.323l-2.834-3.07l-0.562,2.191l-5.458,0.021c-0.552-0.544-1.266-0.934-2.084-1.084c-2.172-0.398-4.256,1.039-4.653,3.211
+	c-0.399,2.176,1.041,4.261,3.212,4.656c0.969,0.178,1.913-0.025,2.705-0.481l4.805,7.734l-6.347,8.857
+	c-2.068-0.231-3.995,1.155-4.374,3.233c-0.399,2.176,1.041,4.261,3.212,4.656c1.112,0.204,2.197-0.078,3.048-0.688l5.882-0.022
+	l0.084,1.77l3.396-2.431l-3.612-2.101L32.628,38.258z M27.257,14.891c0.192-0.188,0.368-0.393,0.521-0.619l5.812,5.694l-1.703,2.376
+	L27.257,14.891z M33.717,27.185l-1.173,0.067l1.002,1.371l-6.161,4.698c-0.393-0.423-0.869-0.772-1.424-0.996l5.865-8.184
+	L33.717,27.185z"/>
+<path fill="#989898" d="M28.403,24.722l4.067-0.016l0.084,1.769l3.396-2.431l-3.612-2.101l0.084,1.762l-3.985,0.016
+	C28.464,24.05,28.462,24.383,28.403,24.722z"/>
+</svg>

Orange/classification/neural.py

 
 class NeuralNetworkLearner(Orange.classification.Learner):
     """
-    NeuralNetworkLearner uses jzbontar's implementation of neural networks and wraps it in
-    an Orange compatible learner. 
+    NeuralNetworkLearner implements a multilayer perceptron. Learning is performed by minimizing an L2-regularized
+    cost function with scipy's implementation of L-BFGS. The current implementations is limited to a single
+    hidden layer. 
 
-    NeuralNetworkLearner supports all types of data and returns a classifier, regression is currently not supported.
-
-    More information about neural networks can be found at http://en.wikipedia.org/wiki/Artificial_neural_network.
+    Regression is currently not supported.
 
     :param name: learner name.
     :type name: string
 
     :param n_mid: Number of nodes in the hidden layer
-    :type n_mid: integer
+    :type n_mid: int
 
     :param reg_fact: Regularization factor.
     :type reg_fact: float
 
     :param max_iter: Maximum number of iterations.
-    :type max_iter: integer
+    :type max_iter: int
+
+    :param normalize: Normalize the data prior to learning (subtract each column by the mean and divide by the standard deviation)
+    :type normalize: bool
 
     :rtype: :class:`Orange.multitarget.neural.neuralNetworkLearner` or 
             :class:`Orange.multitarget.chain.NeuralNetworkClassifier`
             self.__init__(**kwargs)
             return self(data,weight)
 
-    def __init__(self, name="NeuralNetwork", n_mid=10, reg_fact=1, max_iter=1000, rand=None):
+    def __init__(self, name="NeuralNetwork", n_mid=10, reg_fact=1, max_iter=300, normalize=True, rand=None):
         """
         Current default values are the same as in the original implementation (neural_networks.py)
         """
-
         self.name = name
         self.n_mid = n_mid
         self.reg_fact = reg_fact
         self.max_iter = max_iter
         self.rand = rand
+        self.normalize = normalize
 
         if not self.rand:
             self.rand = random.Random(42)
         #converts attribute data
         X = data.to_numpy()[0] 
 
+        mean = X.mean(axis=0)
+        std = X.std(axis=0)
+        if self.normalize:
+            X = (X - mean) / std
+
         #converts multi-target or single-target classes to numpy
-
-
         if data.domain.class_vars:
             for cv in data.domain.class_vars:
                 if cv.var_type == Orange.feature.Continuous:
         
         self.nn.fit(X,Y)
                
-        return NeuralNetworkClassifier(classifier=self.nn.predict, domain = data.domain)
+        return NeuralNetworkClassifier(classifier=self.nn.predict,
+            domain=data.domain, normalize=self.normalize, mean=mean, std=std)
 
 class NeuralNetworkClassifier():
     """    
         if not self.domain.class_vars: example = [example[i] for i in range(len(example)-1)]
         input = np.array([[float(e) for e in example]])
 
+        if self.normalize:
+            input = (input - self.mean) / self.std
+
         # transform results from numpy
         results = self.classifier(input).tolist()[0]
+        if len(results) == 1:
+            prob_positive = results[0]
+            results = [1 - prob_positive, prob_positive]
         mt_prob = []
         mt_value = []
           
     print "STARTED"
     global_timer = time.time()
 
-    data = Orange.data.Table('iris')
-    l1 = NeuralNetworkLearner(n_mid=10, reg_fact=1, max_iter=1000)
-    res = Orange.evaluation.testing.cross_validation([l1],data, 3)
+    data = Orange.data.Table('wine')
+    l1 = NeuralNetworkLearner(n_mid=40, reg_fact=1, max_iter=200)
+
+#    c1 = l1(data)
+#    print c1(data[0], 3), data[0]
+
+    l2 = Orange.classification.bayes.NaiveLearner()
+    res = Orange.evaluation.testing.cross_validation([l1, l2],data, 5)
     scores = Orange.evaluation.scoring.CA(res)
-
     for i in range(len(scores)):
         print res.classifierNames[i], scores[i]
 
-    print "--DONE %.2f --" % (time.time()-global_timer)
+    print "--DONE %.2f --" % (time.time()-global_timer)

Orange/data/discretization.py

 import Orange
 
-from Orange.core import\
-    EquiNDiscretization as EqualFreq,\
-    BiModalDiscretization as BiModal,\
-    Preprocessor_discretize
-
+from Orange.core import Preprocessor_discretize
 
 class DiscretizeTable(object):
     """Discretizes all continuous features of the data table.
 
     """
     def __new__(cls, data=None, features=None, discretize_class=False,
-                method=EqualFreq(n=3), clean=True):
+                method=Orange.feature.discretization.EqualFreq(n=3), clean=True):
         if data is None:
             self = object.__new__(cls)
             return self
             return self(data)
 
     def __init__(self, features=None, discretize_class=False,
-                 method=EqualFreq(n=3), clean=True):
+                 method=Orange.feature.discretization.EqualFreq(n=3), clean=True):
         self.features = features
         self.discretize_class = discretize_class
         self.method = method

Orange/fixes/fix_changed_names.py

 
            "orange.EntropyDiscretization": "Orange.feature.discretization.Entropy",
            "orange.EquiDistDiscretization": "Orange.feature.discretization.EqualWidth",
-           "orange.EquiNDiscretization": "Orange.data.discretization.EqualFreq",
-           "orange.BiModalDiscretization": "Orange.data.discretization.BiModal",
+           "orange.EquiNDiscretization": "Orange.feature.discretization.EqualFreq",
+           "orange.BiModalDiscretization": "Orange.feature.discretization.BiModal",
 
            "orngFSS.attMeasure": "Orange.feature.scoring.score_all",
            "orngFSS.bestNAtts": "Orange.feature.selection.top_rated",

Orange/orng/orngPCA.py

 from numpy import sqrt, abs, dot, transpose
 from numpy.linalg import eig, inv
 
-mathlib_import = True
-try:
-    import matplotlib.pyplot as plt
-except:
-    import warnings
-    warnings.warn("Importing of matplotlib has failed.\nPlotting functions will be unavailable.")
-    mathlib_import = False
-
 #color table for biplot
 Colors = ['bo', 'go', 'yo', 'co', 'mo']
 
         filename : File name under which plot will be saved (default: scree_plot.png)
             If None, plot will be shown
         """
-
-        if not mathlib_import:
-            raise orange.KernelException, "Matplotlib was not imported!"
-
-        #plt.clf() -> opens two windows
+        import pylab as plt
         fig = plt.figure()
         ax = fig.add_subplot(111)
 
         filename : File name under which plot will be saved (default: biplot.png)
             If None, plot will be shown
         """
-
-        if not mathlib_import:
-            raise orange.KernelException, "Matplotlib was not imported!"
+        import pylab as plt
 
         if self._dataMatrix == None:
             raise orange.KernelException, "No data available for biplot!"

Orange/testing/regression/tests_20/exclude-from-regression.txt

 modules_server_files1.py
 modules_server_files2.py
 reference_matrix.py
-__init__.py
+__init__.py
+reference_c45.py

Orange/testing/testing.py

     __IPYTHON__  #We are running tests from ipython
     if getattr(__IPYTHON__.shell, "call_pdb", None): # Is pdb enabled
         enable_pdb()
-except NameError:
+except:
     pass
 
 

Orange/testing/unit/tests/__init__.py

+import os
+import unittest
+if not hasattr(unittest.TestLoader, 'discover'):
+    import unittest2 as unittest
+
+
+def suite():
+    test_dir = os.path.dirname(__file__)
+    return unittest.TestLoader().discover(test_dir, )
+
+test_suite = suite()
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Orange/testing/unit/tests/test_regression.py

     import unittest
 import subprocess
 
+import Orange
 from Orange.utils import environ
 from Orange.testing import testing
 
 
 class TestRegression(unittest.TestCase):
-
     PLATFORM = sys.platform
     PYVERSION = sys.version[:3]
     STATES = ["OK", "timedout", "changed", "random", "error", "crash"]
+    maxDiff = None
+
+    def __init__(self, methodName='runTest'):
+        super(TestRegression, self).__init__(methodName)
+        self.orange_dir = os.path.dirname(Orange.__file__)
+        self.orange_dir = os.path.join(self.orange_dir, '..')
+        self.orange_dir = os.path.realpath(self.orange_dir)
 
     def setUp(self):
-        pass
+        sys.path.append(self.orange_dir)
+
+    def tearDown(self):
+        del sys.path[-1]
 
     def test_regression_on(self, roottest, indir, outdir, name):
         for state in TestRegression.STATES:
 
         stdout, stderr = p.communicate()
         rv = stdout.strip().lower()
+        if rv == 'error':
+            self.assertEqual(stderr.split('\n'), [])
+        elif rv == 'changed':
+            expected_results = self.get_expected_results(outdir, name)
+            actual_results = self.get_actual_results(outdir, name)
+            self.assertEqual(actual_results, expected_results)
+
+
         self.assertEqual(rv, "ok", "Regression test %s: %s" % (rv, name) \
                         if stderr == "" else \
                         "Regression test %s: %s\n\n%s" % (rv, name, stderr))
         os.chdir(tmpdir)
 
+    def get_expected_results(self, outputdir, name):
+        expected_results = "%s/%s.%s.%s.txt" % (outputdir, name, sys.platform, sys.version[:3])
+        if not os.path.exists(expected_results):
+            expected_results = "%s/%s.%s.txt" % (outputdir, name, sys.platform)
+            if not os.path.exists(expected_results):
+                expected_results = "%s/%s.txt" % (outputdir, name)
+
+        with open(expected_results, 'r') as results:
+            return results.read().split('\n')
+
+    def get_actual_results(self, outputdir, name):
+        for state in TestRegression.STATES:
+            actual_results = "%s/%s.%s.%s.%s.txt" % (
+                outputdir, name, TestRegression.PLATFORM,
+                TestRegression.PYVERSION, state)
+
+            if os.path.exists(actual_results):
+                with open(actual_results, 'r') as results:
+                    return results.read().split('\n')
+
+
 root = os.path.normpath(os.path.join(environ.install_dir, ".."))
 roottest = os.path.join(root, "Orange/testing/regression")
 
 editing the setup-site.cfg file in this directory (see the comments
 in that file for instructions on how to do that).
 
+Running tests
+-------------
+After Orange is installed, you can check if everything is working OK by running the included tests::
+
+    python setup.py test
+
+This command runs all the unit tests and documentation examples. Some of the latter have additional dependencies you can satisfy by installing matplotlib, PIL and scipy.
+
 Starting Orange Canvas
 ----------------------
 

docs/development/rst/c.rst

+################################
+Writing Orange Extensions in C++
+################################
+
+This page gives an introduction to extending Orange in C++ with emphasis on
+how to define interfaces to Python. Besides reading this page, we recommend
+studying some of existing extension modules like orangeom, and the Orange's
+interface itself.
+
+We shall first present a general picture and then focus on specific parts of the
+interface.
+
+Instead of general tools for creating interfaces between C++ and Python
+(Swig, Sip, PyBoost...), Orange uses its own specific set of tools.
+
+To expose a C++ object to Python, we need to mark them as exportable, select a
+general constructor template to use or program a specific one, we have to mark
+the attributes to be exported, and provide the interfaces for C++ member
+functions. When we give the access to mostly C++ code as it is, the interface
+functions have only a few lines. When we want to make the exported function more
+friendly, eg. allow various types of arguments or fitting the default arguments
+according to the given ones, these functions are longer.
+
+To define a non-member function, we write the function itself as described in
+the Python's manual (see the first chapter of "Extending and Embedding the
+Python Interpreter") and then mark it with a specific keyword.
+Pyxtract will recognize the keyword and add it to the list of exported functions.
+
+To define a special method, one needs to provide a function with the appropriate
+name constructed from the class name and the special method's name, which is the
+same as in Python's PyTypeObjects.
+
+For instance, the elements of ``ExampleTable`` (examples) can be accessed
+through indexing because we defined a C function that gets an index (and the
+table, of course) and returns the corresponding example. Here is the function
+(with error detection removed for the sake of clarity). ::
+
+    PyObject *ExampleTable_getitem_sq(PyObject *self, int idx)
+    {
+        CAST_TO(TExampleTable, table);
+        return Example_FromExampleRef((*table)[idx], EXAMPLE_LOCK(PyOrange_AsExampleTable(self)));
+    }
+
+Also, ``ExampleTable`` has a non-special method ``sort([list-of-attributes])``.
+This is implemented through a C function that gets a list of attributes and
+calls the C++ class' method
+``TExampleTable::sort(const vector<int> order)``. To illustrate, this is a
+slightly simplified function (we've removed some flexibility regarding the
+parameters and the exception handling). ::
+
+    PyObject *ExampleTable_sort(PyObject *self, PyObject *args) PYARGS(METH_VARARGS, "() -> None")
+    {
+        CAST_TO(TExampleTable, table);
+
+        if (!args || !PyTuple_Size(args)) {
+            table->sort();
+            RETURN_NONE;
+        }
+
+        TVarList attributes;
+        varListFromDomain(PyTuple_GET_ITEM(args, 0), table->domain, attributes, true, true);
+        vector<int> order;
+        for(TVarList::reverse_iterator vi(attributes.rbegin()), ve(attributes.rend()); vi!=ve; vi++) {
+            order.push_back(table->domain->getVarNum(*vi));
+        }
+        table->sort(order);
+        RETURN_NONE;
+    }
+
+The function casts the ``PyObject *`` into the
+corresponding C++ object, reads the arguments, calls the C++
+functions and returns the result (``None``, in this case).
+
+Interfacing with Python requires a lot of manual work, but this gives a
+programmer the opportunity to provide a function which accepts many different
+forms of arguments. The above function, for instance, accepts a list in
+which attributes are specified by indices, names or descriptors, all
+corresponding to the ``ExampleTable`` which is being sorted. Inheritance of
+methods, on the other hand, ensures that only the methods that are truly
+specific for a class need to be coded.
+
+The part of the interface that is built automatically is taken care of by
+two scripts. ``pyprops`` parses all Orange's header files and extracts all
+the class built-in properties. The second is ``pyxtract``, which goes
+through the C++ files that contain the interface functions such as those above.
+It recognizes the functions that implement special or member methods and
+constructs the corresponding ``PyTypeObject``s.
+
+*******
+pyprops
+*******
+
+Pyprops scans each hpp file for classes we want to export to Python). Properties
+can be ``bool``, ``int``, ``float``, ``string``, ``TValue`` or a wrapped Orange
+type.
+
+Class definition needs to look as follows. ::
+
+    class [ORANGE_API] <classname>; [: public <parentclass> ]
+
+This should be in a single line. To mark the class for export, this should be
+followed by ``__REGISTER_CLASS`` or ``__REGISTER_ABSTRACT_CLASS`` before any
+properties or components are defined. The difference between the two, as far as
+pyprops is concerned, is that abstract classes do not define the ``clone``
+method.
+
+To export a property, it should be defined like this. ::
+
+    <type> <name> //P[R|O] [>|+<alias>] <description>
+
+Pyprops doesn't check the type and won't object if you use other types than
+those listed above. The error will be discovered later, during linking. ``//P``
+signals that we want to export the property. If followed by ``R`` or ``O``, the
+property is read-only or obsolete. The property can also have an alias name;
+``>`` renames it and ``+`` adds an alias.
+
+Each property needs to be declared in a separate line, e.g. ::
+
+    int x; //P;
+    int y; //P;
+
+If we don't want to export a certain property, we omit the ``//P`` mark. An
+exception to this are wrapped Orange objects: for instance, if a class has a
+(wrapped) pointer to the domain, ``PDomain`` and it doesn't export it, pyxtract
+should still know about them because for the purpose of garbage collection. You
+should mark them by ``//C`` so that they are put into the list of objects that
+need to be counted. Failing to do so would cause a memory leak.
+
+If a class directly or indirectly holds references to any wrapped objects that
+are neither properties nor components, it needs to declare ``traverse`` and
+``clear`` as described in Python documentation.
+
+Pyprops creates a ppp file for each hpp, which includes the extracted
+information in form of C++ structures that compile into the interface.
+The ppp file needs to be included in the corresponding cpp file. For
+instance, domain.ppp is included in domain.cpp.
+
+********
+pyxtract
+********
+
+Pyxtract's job is to detect the functions that define special methods (such as
+printing, conversion, sequence and arithmetic related operations...) and member
+functions. Based on what it finds for each specific class, it constructs the
+corresponding ``PyTypeObject``s. For the functions to be recognized, they must
+follow a specific syntax.
+
+There are two basic mechanisms for marking the functions to export. Special
+functions are recognized by their definition (they need to return
+``PyObject *``, ``void`` or ``int`` and their name must be of form
+<classname>_<functionname>). Member functions,
+inheritance relations, constants etc. are marked by macros such as ``PYARGS``
+in the above definition of ``ExampleTable_sort``. Most of these macros don't do
+anything except for marking stuff for pyxtract.
+
+Class declaration
+=================
+
+Each class needs to be declared as exportable. If it's a base class, pyxtract
+needs to know the data structure for the instances of this class. As for all
+Python objects the structure must be "derived" from ``PyObject`` (Python is
+written in C, so the subclasses are not derived in the C++ sense but extend the
+C structure instead). Most objects are derived from Orange; the only exceptions
+are ``orange.Example``, ``orange.Value`` and ``orange.DomainDepot``.
+
+Pyxtract should also know how the class is constructed - it can have a specific
+constructor, one of the general constructors or no constructor at all.
+
+The class is declared in one of the following ways (here are some examples from
+actual Orange code).
+
+``BASED_ON(EFMDataDescription, Orange)``
+    This tells pyxtract that ``EFMDataDescription`` is an abstract class derived from ``Orange``: there is no constructor for this class in Python, but the C++ class itself is not abstract and can appear and be used in Python. For example, when we construct an instance of ``ClassifierByLookupTable`` with more than three attributes, an instance of ``EFMDataDescription`` will appear in one of its fields.
+
+``ABSTRACT(ClassifierFD, Classifier)``
+    This defines an abstract class, which will never be constructed in the C++ code. The only difference between this ``BASED_ON`` and ``ABSTRACT`` is that the former can have pickle interface, while the latter don't need one.
+
+Abstract C++ classes are not necessarily defined as ``ABSTRACT`` in the Python
+interface. For example, ``TClassifier`` is an abstract C++ class, but you can
+seemingly construct an instance of ``Classifier`` in Python. What happens is
+that there is an additional C++ class ``TClassifierPython``, which poses as
+Python's class ``Classifier``. So the Python class ``Classifier`` is not defined
+as ``ABSTRACT`` or ``BASED_ON`` but using the ``Classifier_new`` function, as
+described below.
+
+
+``C_NAMED(EnumVariable, Variable, "([name=, values=, autoValues=, distributed=, getValueFrom=])")``
+    ``EnumVariable`` is derived from ``Variable``. Pyxtract will also create a constructor which will accept the object's name as an optional argument. The third argument is a string that describes the constructor, eg. gives a list of arguments. IDEs for Python, such as PythonWin, will show this string in a balloon help while the programmer is typing.
+
+``C_UNNAMED(RandomGenerator, Orange, "() -> RandomGenerator")``
+    This is similar as ``C_NAMED``, except that the constructor accepts no name. This form is rather rare since all Orange objects can be named.
+
+``C_CALL(BayesLearner, Learner, "([examples], [weight=, estimate=] -/-> Classifier")``
+    ``BayesLearner`` is derived from ``Learner``. It will have a peculiar constructor. It will, as usual, first construct an instance of ``BayesLearner``. If no arguments are given (except for, possibly, keyword arguments), it will return the constructed instance. Otherwise, it will call the ``Learner``'s call operator and return its result instead of ``BayesLearner``.
+
+``C_CALL3(MakeRandomIndices2, MakeRandomIndices2, MakeRandomIndices, "[n | gen [, p0]], [p0=, stratified=, randseed=] -/-> [int]")``
+    ``MakeRandomIndices2`` is derived from ``MakeRandomIndices`` (the third argument). For a contrast from the ``C_CALL`` above, the corresponding constructor won't call ``MakeRandomIndices`` call operator, but the call operator of ``MakeRandomIndices2`` (the second argument). This constructor is often used when the parent class doesn't provide a suitable call operator.
+
+``HIDDEN(TreeStopCriteria_Python, TreeStopCriteria)``
+    ``TreeStopCriteria_Python`` is derived from ``TreeStopCriteria``, but we would like to hide this class from the user. We use this definition when it is elegant for us to have some intermediate class or a class that implements some specific functionality, but don't want to bother the user with it. The class is not completely hidden - the user can reach it through the ``type`` operator on an instance of it. This is thus very similar to a ``BASED_ON``.
+
+``DATASTRUCTURE(Orange, TPyOrange, orange_dict)``
+    This is for the base classes. ``Orange`` has no parent class. The C++ structure that stores it is ``TPyOrange``; ``TPyOrange`` is essentially ``PyObject`` (again, the structure always has to be based on ``PyObject``) but with several additional fields, among them a pointer to an instance of ``TOrange`` (the C++ base class for all Orange's classes). ``orange_dict`` is a name of ``TPyOrange``'s field that points to a Python dictionary; when you have an instance ``bayesClassifier`` and you type, in Python, ``bayesClassifier.someMyData=15``, this gets stored in ``orange_dict``. The actual mechanism behind this is rather complicated and you most probably won't need to use it. If you happen to need to define a class with ``DATASTRUCTURE``, you can simply omit the last argument and give a 0 instead.
+
+Even if the class is defined by ``DATASTRUCTURE``, you can still specify a
+different constructor, most probably the last form of it (the ``_new``
+function). In this case, specify a keyword ``ROOT`` as a parent and pyxtract
+will understand that this is the base class.
+
+Object construction in Python is divided between two methods. The constructors
+we discussed above construct the essential part of the object - they allocate
+the necessary memory and initialize the fields far enough that the object is
+valid to enter the garbage collection. The second part is handled by the
+``init`` method. It is, however, not forbidden to organize the things so that
+``new`` does all the job. This is also the case in Orange. The only task left
+for ``init`` is to set any attributes that user gave as the keyword arguments to
+the constructor.
+
+For instance, Python's statement
+``orange.EnumVariable("a", values=["a", "b", "c"])`` is executed so that ``new``
+constructs the variable and gives it the name, while ``init`` sets the
+``values`` field.
+
+The ``new`` operator can also accept keyword arguments. For
+instance, when constructing an ``ExampleTable`` by reading the data from a file,
+you can specify a domain (using keyword argument ``domain``), a list of
+attributes to reuse if possible (``use``), you can tell it not to reuse the
+stored domain or not to store the newly constructed domain (``dontCheckStored``,
+``dontStore``). After the ``ExampleTable`` is constructed, ``init`` is called to
+set the attributes. To tell it to ignore the keyword arguments that the
+constructor might (or had) used, we write the following. ::
+
+    CONSTRUCTOR_KEYWORDS(ExampleTable, "domain use useMetas dontCheckStored dontStore filterMetas")
+
+There's another macro related to attributes. Let ``ba`` be an orange object, say
+an instance of ``orange.BayesLearner``. If you assign new attributes as usual
+directly, eg. ``ba.myAttribute = 12``, you will get a warning (you should use
+the object's method ``setattr(name, value)`` to avoid it). Some objects have
+some attributes that cannot be implemented in C++ code, yet they are usual and
+useful. For instance, ``Graph`` can use attributes ``objects``, ``forceMapping``
+and ``returnIndices``, which can only be set from Python (if you take a look at
+the documentation on ``Graph`` you will see why these cannot be implemented in
+C++). Yet, since user are allowed to set these attributes and will do so often,
+we don't want to give warnings. We achieve this by ::
+
+    RECOGNIZED_ATTRIBUTES(Graph, "objects forceMapping returnIndices")
+
+
+Special methods
+===============
+
+Special methods act as the class built-in methods. They define what the type can
+do: if it, for instance, supports multiplication, it should define the operator
+that gets the object itself and another object and return the product (or throw
+an exception). If it allows for indexing, it defines an operator that gets the
+object itself and the index, and returns the element. These operators are
+low-level; most can be called from Python scripts but they are also internally
+by Python. For instance, if ``table`` is an ``ExampleTable``, then
+``for e in table:`` or ``reduce(f, table)`` will both work by calling the
+indexing operator for each table's element.
+For more details, consider the Python manual, chapter "Extending and
+Embedding the Python Interpreter" section "Defining New Types".
+
+To define a method for Orange class, you need to define a function named,
+``<classname>_<methodname>``; the function should return either
+``PyObject *``, ``int`` or ``void``. The function's head has to be written in a
+single line. Regarding the arguments and the result, it should conform to
+Python's specifications. Pyxtract will detect the methods and set the pointers
+in ``PyTypeObject`` correspondingly.
+
+Here is a list of methods: the left column represents a method name that
+triggers pyxtract (these names generally correspond to special method names of
+Python classes as a programmer in Python sees them) and the second is the
+name of the field in ``PyTypeObject`` or subjugated structures. See Python
+documentation for description of functions' arguments and results. Not all
+methods can be directly defined; for those that can't, it is because we either
+use an alternative method (eg. ``setattro`` instead of ``setattr``) or pyxtract
+gets or computes the data for this field in some other way.
+
+General methods
+---------------
+
++--------------+-----------------------+-----------------------------------------------------------+
+| pyxtract     | PyTypeObject          |                                                           |
++==============+=======================+===========================================================+
+| ``dealloc``  | ``tp_dealloc``        | Frees the memory occupied by the object. You will need to |
+|              |                       | define this for the classes with a new ``DATASTRUCTURE``; |
+|              |                       | if you only derive a class from some Orange class, this   |
+|              |                       | has been taken care of. If you have a brand new object,   |
+|              |                       | copy the code of one of Orange's deallocators.            |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_getattr``        | Can't be redefined since we use ``tp_getattro`` instead.  |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_setattr``        | Can't be redefined since we use ``tp_setattro`` instead.  |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``cmp``      | ``tp_compare``        |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``repr``     | ``tp_repr``           |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``as_number``         | (pyxtract will initialize this field if you give any of   |
+|              |                       | the methods from the number protocol; you needn't care    |
+|              |                       | about this field)                                         |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``as_sequence``       | (pyxtract will initialize this field if you give any of   |
+|              |                       | the methods from the sequence protocol)                   |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``as_mapping``        | (pyxtract will initialize this field if you give any of   |
+|              |                       | the methods from the mapping protocol)                    |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``hash``     | ``tp_hash``           | Class ``Orange`` computes a hash value from the pointer;  |
+|              |                       | you don't need to overload it if your object inherits the |
+|              |                       | function. If you write an independent class, just copy the|
+|              |                       | code.                                                     |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``call``     | ``tp_call``           |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``call``     | ``tp_call``           |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``str``      | ``tp_str``            |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``getattr``  | ``tp_getattro``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``setattr``  | ``tp_setattro``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_as_buffer``      | Pyxtract doesn't support the buffer protocol.             |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_flags``          | Flags are set by pyxtract.                                |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_doc``            | Documentation is read from the constructor definition     |
+|              |                       | (see above).                                              |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``traverse`` | ``tp_traverse``       | Traverse is tricky (as is garbage collection in general). |
+|              |                       | There's something on it in a comment in root.hpp; besides |
+|              |                       | that, study the examples. In general, if a wrapped member |
+|              |                       | is exported to Python (just as, for instance,             |
+|              |                       | ``Classifier`` contains a ``Variable`` named              |
+|              |                       | ``classVar``), you don't need to care about it. You should|
+|              |                       | manually take care of any wrapped objects not exported to |
+|              |                       | Python. You probably won't come across such cases.        |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``clear``    | ``tp_clear``          |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``richcmp``  | ``tp_richcmp``        |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_weaklistoffset`` |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``iter``     | ``tp_iter``           |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``iternext`` | ``tp_iternext``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_methods``        | Set by pyxtract if any methods are given.                 |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_members``        |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``getset``            | Pyxtract initializes this by a pointer to manually        |
+|              |                       | written getters/setters (see below).                      |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_base``           | Set by pyxtract to a class specified in constructor       |
+|              |                       | (see above).                                              |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_dict``           | Used for class constants (eg. ``Classifier.GetBoth``)     |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_descrget``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_descrset``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_dictoffset``     | Set by pyxtract to the field given in ``DATASTRUCTURE``   |
+|              |                       | (if there is any).                                        |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``init``     | ``tp_init``           |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_alloc``          | Set to ``PyType_GenericAlloc``                            |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``new``      | ``tp_new``            |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_free``           | Set to ``_PyObject_GC_Del``                               |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_is_gc``          |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_bases``          |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_mro``            |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_cache``          |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_subclasses``     |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+| ``.``        | ``tp_weaklist``       |                                                           |
++--------------+-----------------------+-----------------------------------------------------------+
+
+Numeric protocol
+----------------
+
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``add``    |  ``nb_add``      | ``pow``     | ``nb_power``    | ``lshift`` | ``nb_lshift`` | ``int``   | ``nb_int``   |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``sub``    | ``nb_subtract``  | ``neg``     | ``nb_negative`` | ``rshift`` | ``nb_rshift`` | ``long``  | ``nb_long``  |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``mul``    | ``nb_multiply``  | ``pos``     | ``nb_positive`` | ``and``    | ``nb_and``    | ``float`` | ``nb_float`` |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``div``    | ``nb_divide``    | ``abs``     | ``nb_absolute`` | ``or``     | ``nb_or``     | ``oct``   | ``nb_oct``   |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``mod``    | ``nb_remainder`` | ``nonzero`` | ``nb_nonzero``  | ``coerce`` | ``nb_coerce`` | ``hex``   | ``nb_hex``   |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+| ``divmod`` | ``nb_divmod``    | ``inv``     | ``nb_invert``   |            |               |           |              |
++------------+------------------+-------------+-----------------+------------+---------------+-----------+--------------+
+
+Sequence protocol
+-----------------
+
++----------------+---------------+----------------+------------------+
+| ``len_sq``     | ``sq_length`` | ``getslice``   | ``sq_slice``     |
++----------------+---------------+----------------+------------------+
+| ``concat``     | ``sq_concat`` | ``setitem_sq`` | ``sq_ass_item``  |
++----------------+---------------+----------------+------------------+
+| ``repeat``     | ``sq_slice``  | ``setslice``   | ``sq_ass_slice`` |
++----------------+---------------+----------------+------------------+
+| ``getitem_sq`` | ``sq_item``   | ``contains``   | ``sq_contains``  |
++----------------+---------------+----------------+------------------+
+
+Mapping protocol
+----------------
+
++-------------+----------------------+
+| ``len``     | ``mp_length``        |
++-------------+----------------------+
+| ``getitem`` | ``mp_subscript``     |
++-------------+----------------------+
+| ``setitem`` | ``mp_ass_subscript`` |
++-------------+----------------------+
+
+For example, here is what gets called when you want to know the length of an
+example table. ::
+
+    int ExampleTable_len_sq(PyObject *self)
+    {
+        PyTRY
+            return SELF_AS(TExampleGenerator).numberOfExamples();
+        PyCATCH_1
+    }
+
+``PyTRY`` and ``PyCATCH`` take care of C++ exceptions. ``SELF_AS`` is a macro
+for casting, ie unwrapping the points (this is an alternative to ``CAST_TO``).
+
+
+Getting and Setting Class Attributes
+====================================
+
+Exporting of most of C++ class fields is already taken care by the lists that
+are compiled by pyprops. There are only a few cases in the entire Orange where
+we needed to manually write specific handlers for setting and getting the
+attributes. This needs to be done if setting needs some special processing or
+when simulating an attribute that does not exist in the underlying C++ class.
+
+An example for this is class ``HierarchicalCluster``. It contains results of a
+general, not necessarily binary clustering, so each node in the tree has a list
+``branches`` with all the node's children. Yet, as the usual clustering is
+binary, it would be nice if the node would also support attributes ``left`` and
+``right``. They are not present in C++, but we can write a function that check
+the number of branches; if there are none, it returns ``None``, if there are
+more than two, it complains, while otherwise it returns the first branch. ::
+
+    PyObject *HierarchicalCluster_get_left(PyObject *self)
+    {
+        PyTRY
+            CAST_TO(THierarchicalCluster, cluster);
+
+            if (!cluster->branches)
+                RETURN_NONE
+
+            if (cluster->branches->size() > 2)
+                PYERROR(PyExc_AttributeError,
+                        "'left' not defined (cluster has more than two subclusters)",
+                        NULL);
+
+            return WrapOrange(cluster->branches->front());
+        PyCATCH
+    }
+
+As you can see from the example, the function needs to accept a ``PyObject *``
+(the object it``self``) and return a ``PyObject *`` (the attribute value). The
+function name needs to be ``<classname>_get_<attributename>``.
+Setting an attribute is similar; function name should be
+``<classname>_set_<attributename>``, it should accept two Python
+objects (the object and the attribute value) and return an ``int``, where 0
+signifies success and -1 a failure.
+
+If you define only one of the two handlers, you'll get a read-only or write-only
+attribute.
+
+
+Member functions
+================
+
+We have already shown an example of a member function - the ``ExampleTable``'s
+method ``sort``. The general template is
+``PyObject *<classname>_<methodname>(<arguments>) PYARGS(<arguments-keyword>, <documentation-string>)``.
+In the case of the ``ExampleTable``'s ``sort``, this looks like this. ::
+
+    PyObject *ExampleTable_sort(PyObject *self, PyObject *args) PYARGS(METH_VARARGS, "() -> None")
+
+Argument type can be any of the usual Python constants stating the number and
+the kind of arguments, such as ``METH_VARARGS`` or ``METH_O`` - this constant
+gets copied to the corresponding list (browse Python documentation for
+``PyMethodDef``).
+
+
+Class constants
+===============
+
+Orange classes, as seen from Python, can also have constants, such as
+``orange.Classifier.GetBoth``. Classifier's ``GetBoth`` is visible as a member
+of the class, the derived classes and all their instances (eg.
+``BayesClassifier.GetBoth`` and ``bayes.GetBoth``).
+
+There are several ways to define such constants. If they are simple integers or
+floats, you can use ``PYCLASSCONSTANT_INT`` or ``PYCLASSCONSTANT_FLOAT``, like
+in ::
+
+    PYCLASSCONSTANT_INT(Classifier, GetBoth, 2)
+
+You can also use the enums from the class, like ::
+
+    PYCLASSCONSTANT_INT(C45TreeNode, Leaf, TC45TreeNode::Leaf)
+
+Pyxtract will convert the given constant to a Python object (using
+``PyInt_FromLong`` or ``PyFloat_FromDouble>``).
+
+When the constant is an object of some other type, use ``PYCLASSCONSTANT``. In
+this form (not used in Orange so far), the third argument can be either an
+instance of ``PyObject *`` or a function call. In either case, the object or
+function must be known at the point where the pyxtract generated file is
+included.
+
+
+Pickling
+========
+
+Pickling is taken care of automatically if the class provides a Python
+constructor that can construct the object without arguments (it may *accept*
+arguments, but should be able to do without them. If there is no such
+constructor, the class should provide a ``__reduce__`` method or it should
+explicitly declare that it cannot be pickled. If it doesn't pyxtract will issue
+a warning that the class will not be picklable.
+
+Here are the rules:
+
+* Classes that provide a ``__reduce__`` method (details follow below) are pickled through that method.
+
+* Class ``Orange``, the base class, already provides a ``__reduce__`` method, which is only useful if the constructor accepts empty arguments. So, if the constructor is declared as ``C_NAMED``, ``C_UNNAMED``, ``C_CALL`` or ``C_CALL3``, the class is the class will be picklable. See the warning below.
+
+* If the constructor is defined by ``_new`` method, and the ``BASED_ON`` definition is followed be ``ALLOWS_EMPTY``, this signifies that it accepts empty arguments, so it will be picklable just as in the above point. For example, the constructor for the class ``DefaultClassifier`` is defined like this ::
+
+    PyObject *DefaultClassifier_new(PyTypeObject *tpe, PyObject *args)
+        BASED_ON(Classifier, "([defaultVal])") ALLOWS_EMPTY
+
+and is picklable through code ``Orange.__reduce__``. But again, see the warning
+below.
+
+* If the constructor is defined as ``ABSTRACT``, there cannot be any instances of this class, so pyxtract will give no warning that it is not picklable.
+* The class can be explicitly defined as not picklable by ``NO_PICKLE`` macro, as in ::
+
+    NO_PICKLE(TabDelimExampleGenerator)
+
+  Such classes won't be picklable even if they define the appropriate
+  constructors. This effectively defined a ``__reduce__`` method which yields an
+  exception; if you manually provide a ``__reduce__`` method for such a class,
+  pyxtract will detect that the method is multiply defined.
+
+* If there are no suitable constructors, no ``__reduce__`` method and no
+  ``ABSTRACT`` or ``NO_PICKLE`` flag, pyxtract gives a warning about that.
+
+When the constructor is used, as in points 2 and 3, pickling will only work if
+all fields of the C++ class can be set "manually" from Python, are set through
+the constructor, or are set when assigning other fields. In other words, if
+there are fields that are not
+marked as ``//P`` for pyprops, you will most probably need to manually define
+a ``__reduce__`` method, as in point 1.
+
+The details of what the ``__reduce__`` method must do are described in the
+Python documentation. In our circumstances, it can be implemented in two ways
+which differ in what function is used for unpickling: it can either use the
+class' constructor or we can define a special method for unpickling.
+
+The former usually happens when the class has a read-only property (``//PR``),
+which is set by the constructor. For instance, ``AssociationRule`` has read-only
+fields ``left`` and ``right``, which are needs to be given to the constructor.
+This is the ``__reduce__`` method for the class. ::
+
+    PyObject *AssociationRule__reduce__(PyObject *self)
+    {
+        PyTRY
+            CAST_TO(TAssociationRule, arule);
+            return Py_BuildValue("O(NN)N", self->ob_type,
+                                       Example_FromWrappedExample(arule->left),
+                                       Example_FromWrappedExample(arule->right),
+                                       packOrangeDictionary(self));
+        PyCATCH
+    }
+
+As described in the Python documentation, the ``__reduce__`` should return a
+tuple in which the first element is the function that will do the unpickling,
+and the second argument are the arguments for that function. Our unpickling
+function is simply the classes' type (calling a type corresponds to calling a
+constructor) and the arguments for the constructor are the left- and right-hand
+side of the rule. The third element of the tuple is classes' dictionary.
+
+When unpickling is more complicated - usually when the class has no constructor
+and contains fields of type ``float *`` or similar - we need a special
+unpickling function. The function needs to be directly in the modules' namespace
+(it cannot be a static method of a class), so we named them
+``__pickleLoader<classname>``. Search for examples of such functions in
+the source code; note that the instance's true class need to be pickled, too.
+Also, check how we use ``TCharBuffer`` throughout the code to store and pickle
+binary data as Python strings.
+
+Be careful when manually writing the unpickler: if a C++ class derived from that
+class inherits its ``__reduce__``, the corresponding unpickler will construct an
+instance of a wrong class (unless the unpickler functions through Python's
+constructor, ``ob_type->tp_new``). Hence, classes derived from a class which
+defines an unpickler have to define their own ``__reduce__``, too.
+
+Non-member functions and constants
+==================================
+
+Non-member functions are defined in the same way as member functions except
+that their names do not start with the class name. Here is how the ``newmetaid``
+is implemented ::
+
+    PyObject *newmetaid(PyObject *, PyObject *) PYARGS(0,"() -> int")
+    {
+        PyTRY
+            return PyInt_FromLong(getMetaID());
+        PyCATCH
+    }
+
+Orange also defines some non-member constants. These are defined in a similar
+fashion as the class constants.
+``PYCONSTANT_INT(<constant-name>, <integer>)`` defines an integer
+constant and ``PYCONSTANT_FLOAT`` would be used for a continuous one.
+``PYCONSTANT`` is used for objects of other types, as the below example that
+defines an (obsolete) constant ``MeasureAttribute_splitGain`` shows. ::
+
+    PYCONSTANT(MeasureAttribute_splitGain, (PyObject *)&PyOrMeasureAttribute_gainRatio_Type)
+
+Class constants from the previous section are put in a pyxtract generated file
+that is included at the end of the file in which the constant definitions and
+the corresponding classes are. Global constant modules are included in another
+file, far away from their actual definitions. For this reason, ``PYCONSTANT``
+cannot refer to any functions (the above example is an exception - all class
+types are declared in this same file and are thus available at the moment the
+above code is used). Therefore, if the constant is defined by a function call,
+you need to use another keyword, ``PYCONSTANTFUNC``::
+
+    PYCONSTANTFUNC(globalRandom, stdRandomGenerator)
+
+Pyxtract will generate a code which will, prior to calling
+``stdRandomGenerator``, declare it as a function with no arguments that returns
+``PyObject *``. Of course, you will have to define the function somewhere in
+your code, like this::
+
+    PyObject *stdRandomGenerator()
+    {
+        return WrapOrange(globalRandom);
+    }
+
+Another example are ``VarTypes``. ``VarTypes`` is a tiny module inside Orange
+that contains nothing but five constants, representing various attribute types.
+From pyxtract perspective, ``VarTypes`` is a constant. This is the complete
+definition. ::
+
+    PyObject *VarTypes()
+    {
+        PyObject *vartypes=PyModule_New("VarTypes");
+        PyModule_AddIntConstant(vartypes, "None", (int)TValue::NONE);
+        PyModule_AddIntConstant(vartypes, "Discrete", (int)TValue::INTVAR);
+        PyModule_AddIntConstant(vartypes, "Continuous", (int)TValue::FLOATVAR);
+        PyModule_AddIntConstant(vartypes, "Other", (int)TValue::FLOATVAR+1);
+        PyModule_AddIntConstant(vartypes, "String", (int)STRINGVAR);
+        return vartypes;
+    }
+
+    PYCONSTANTFUNC(VarTypes, VarTypes)
+
+If you want to understand the constants completely, check the Orange's pyxtract
+generated file initialization.px.
+
+How does it all fit together
+============================
+