2020 QWidget , QListView , QTreeView , QVBoxLayout , QHBoxLayout , QFormLayout ,
2121 QToolButton , QLineEdit , QAction , QActionGroup , QStackedWidget , QGroupBox ,
2222 QStyledItemDelegate , QStyleOptionViewItem , QStyle , QSizePolicy , QToolTip ,
23- QDialogButtonBox , QPushButton
23+ QDialogButtonBox , QPushButton , QCheckBox
2424)
2525from AnyQt .QtGui import QStandardItemModel , QStandardItem , QKeySequence , QIcon
2626from AnyQt .QtCore import pyqtSignal as Signal , pyqtSlot as Slot
@@ -51,6 +51,10 @@ class Categorical(
5151 ])): pass
5252
5353
54+ class Ordered (Categorical ):
55+ pass
56+
57+
5458class Real (
5559 NamedTuple ("Real" , [
5660 ("name" , str ),
@@ -130,8 +134,14 @@ def __call__(self, var):
130134 return var ._replace (annotations = self .annotations )
131135
132136
133- Transform = Union [Rename , CategoriesMapping , Annotate ]
134- TransformTypes = (Rename , CategoriesMapping , Annotate )
137+ class ChangeOrdered (NamedTuple ("ChangeOrdered" , [("ordered" , bool )])):
138+ """
139+ Change Categorical <-> Ordered
140+ """
141+
142+
143+ Transform = Union [Rename , CategoriesMapping , Annotate , ChangeOrdered ]
144+ TransformTypes = (Rename , CategoriesMapping , Annotate , ChangeOrdered )
135145
136146
137147def deconstruct (obj ):
@@ -465,7 +475,10 @@ def __init__(self, *args, **kwargs):
465475 super ().__init__ (* args , ** kwargs )
466476 form = self .layout ().itemAt (0 )
467477 assert isinstance (form , QFormLayout )
468-
478+ self .ordered_cb = QCheckBox (
479+ "Ordered" , self , toolTip = "Is this an ordered categorical."
480+ )
481+ self .ordered_cb .toggled .connect (self ._set_ordered )
469482 #: A list model of discrete variable's values.
470483 self .values_model = itemmodels .PyListModel (
471484 flags = Qt .ItemIsSelectable | Qt .ItemIsEnabled | Qt .ItemIsEditable
@@ -553,9 +566,12 @@ def __init__(self, *args, **kwargs):
553566 hlayout .addStretch (10 )
554567 vlayout .addLayout (hlayout )
555568
556- form .insertRow (1 , "Values:" , vlayout )
569+ form .insertRow (1 , "" , self .ordered_cb )
570+ form .insertRow (2 , "Values:" , vlayout )
571+
572+ QWidget .setTabOrder (self .name_edit , self .ordered_cb )
573+ QWidget .setTabOrder (self .ordered_cb , self .values_edit )
557574
558- QWidget .setTabOrder (self .name_edit , self .values_edit )
559575 QWidget .setTabOrder (self .values_edit , button1 )
560576 QWidget .setTabOrder (button1 , button2 )
561577 QWidget .setTabOrder (button2 , button3 )
@@ -568,9 +584,12 @@ def set_data(self, var, transform=()):
568584 """
569585 super ().set_data (var , transform )
570586 tr = None # type: Optional[CategoriesMapping]
587+ ordered = None # type: Optional[ChangeOrdered]
571588 for tr_ in transform :
572589 if isinstance (tr_ , CategoriesMapping ):
573590 tr = tr_
591+ if isinstance (tr_ , ChangeOrdered ):
592+ ordered = tr_
574593
575594 items = []
576595 if tr is not None :
@@ -622,6 +641,10 @@ def set_data(self, var, transform=()):
622641 self .values_model .index (i , 0 ),
623642 item
624643 )
644+ if ordered is not None :
645+ self .ordered_cb .setChecked (ordered .ordered )
646+ elif var is not None :
647+ self .ordered_cb .setChecked (isinstance (var , Ordered ))
625648 self .add_new_item .actionGroup ().setEnabled (var is not None )
626649
627650 def __categories_mapping (self ):
@@ -657,6 +680,9 @@ def get_data(self):
657680 if any (_1 != _2 or _2 != _3
658681 for (_1 , _2 ), _3 in zip_longest (mapping , var .categories )):
659682 tr .append (CategoriesMapping (mapping ))
683+ ordered = self .ordered_cb .isChecked ()
684+ if ordered != isinstance (var , Ordered ):
685+ tr .append (ChangeOrdered (ordered ))
660686 return var , tr
661687
662688 def clear (self ):
@@ -753,6 +779,10 @@ def _add_category(self):
753779 view .edit (index )
754780 self .on_values_changed ()
755781
782+ def _set_ordered (self , ordered ):
783+ self .ordered_cb .setChecked (ordered )
784+ self .variable_changed .emit ()
785+
756786
757787class ContinuousVariableEditor (VariableEditor ):
758788 # TODO: enable editing of display format...
@@ -766,7 +796,7 @@ class TimeVariableEditor(VariableEditor):
766796
767797def variable_icon (var ):
768798 # type: (Variable) -> QIcon
769- if isinstance (var , Categorical ):
799+ if isinstance (var , ( Categorical , Ordered ) ):
770800 return gui .attributeIconDict [1 ]
771801 elif isinstance (var , Real ):
772802 return gui .attributeIconDict [2 ]
@@ -1037,7 +1067,7 @@ def open_editor(self, index):
10371067 tr = []
10381068
10391069 editors = {
1040- Categorical : 0 ,
1070+ Categorical : 0 , Ordered : 0 ,
10411071 Real : 1 ,
10421072 Time : 2 ,
10431073 String : 3
@@ -1248,7 +1278,7 @@ def i(text):
12481278 def text (text ):
12491279 return "<span>{}</span>" .format (escape (text ))
12501280 assert trs
1251- rename = annotate = catmap = None
1281+ rename = annotate = catmap = ordered = None
12521282
12531283 for tr in trs :
12541284 if isinstance (tr , Rename ):
@@ -1257,10 +1287,17 @@ def text(text):
12571287 annotate = tr
12581288 elif isinstance (tr , CategoriesMapping ):
12591289 catmap = tr
1290+ elif isinstance (tr , ChangeOrdered ):
1291+ ordered = tr
1292+
12601293 if rename is not None :
12611294 header = "{} → {}" .format (var .name , rename .name )
12621295 else :
12631296 header = var .name
1297+ if ordered is not None and ordered .ordered != isinstance (var , Ordered ):
1298+ assert isinstance (var , (Categorical , Ordered ))
1299+ header += " (changed to {})" .format (
1300+ "ordered" if ordered .ordered else "unordered" )
12641301 values_section = None
12651302 if catmap is not None :
12661303 values_section = ("Values" , [])
@@ -1328,7 +1365,10 @@ def abstract(var):
13281365 if isinstance (var , Orange .data .DiscreteVariable ):
13291366 values , base = var .values , var .base_value
13301367 base = values [base ] if base >= 0 else None
1331- return Categorical (var .name , tuple (values ), base , annotations )
1368+ if var .ordered :
1369+ return Ordered (var .name , tuple (values ), base , annotations )
1370+ else :
1371+ return Categorical (var .name , tuple (values ), base , annotations )
13321372 elif isinstance (var , Orange .data .TimeVariable ):
13331373 return Time (var .name , annotations )
13341374 elif isinstance (var , Orange .data .ContinuousVariable ):
@@ -1363,13 +1403,16 @@ def apply_transform_discete(var, trs):
13631403 name , annotations = var .name , var .attributes
13641404 base_value = var .base_value
13651405 mapping = None
1406+ ordered = var .ordered
13661407 for tr in trs :
13671408 if isinstance (tr , Rename ):
13681409 name = tr .name
13691410 elif isinstance (tr , CategoriesMapping ):
13701411 mapping = tr .mapping
13711412 elif isinstance (tr , Annotate ):
13721413 annotations = _parse_attributes (tr .annotations )
1414+ elif isinstance (tr , ChangeOrdered ):
1415+ ordered = tr .ordered
13731416
13741417 source_values = var .values
13751418 if mapping is not None :
@@ -1399,7 +1442,8 @@ def positions(values):
13991442 else :
14001443 lookup = Identity (var )
14011444 variable = Orange .data .DiscreteVariable (
1402- name , values = dest_values , base_value = base_value , compute_value = lookup
1445+ name , values = dest_values , base_value = base_value , compute_value = lookup ,
1446+ ordered = ordered ,
14031447 )
14041448 variable .attributes .update (annotations )
14051449 return variable
0 commit comments