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 )
@@ -566,11 +582,15 @@ def set_data(self, var, transform=()):
566582 """
567583 Set the variable to edit.
568584 """
585+ # pylint: disable=too-many-branches
569586 super ().set_data (var , transform )
570587 tr = None # type: Optional[CategoriesMapping]
588+ ordered = None # type: Optional[ChangeOrdered]
571589 for tr_ in transform :
572590 if isinstance (tr_ , CategoriesMapping ):
573591 tr = tr_
592+ if isinstance (tr_ , ChangeOrdered ):
593+ ordered = tr_
574594
575595 items = []
576596 if tr is not None :
@@ -622,6 +642,10 @@ def set_data(self, var, transform=()):
622642 self .values_model .index (i , 0 ),
623643 item
624644 )
645+ if ordered is not None :
646+ self .ordered_cb .setChecked (ordered .ordered )
647+ elif var is not None :
648+ self .ordered_cb .setChecked (isinstance (var , Ordered ))
625649 self .add_new_item .actionGroup ().setEnabled (var is not None )
626650
627651 def __categories_mapping (self ):
@@ -657,6 +681,9 @@ def get_data(self):
657681 if any (_1 != _2 or _2 != _3
658682 for (_1 , _2 ), _3 in zip_longest (mapping , var .categories )):
659683 tr .append (CategoriesMapping (mapping ))
684+ ordered = self .ordered_cb .isChecked ()
685+ if ordered != isinstance (var , Ordered ):
686+ tr .append (ChangeOrdered (ordered ))
660687 return var , tr
661688
662689 def clear (self ):
@@ -753,6 +780,10 @@ def _add_category(self):
753780 view .edit (index )
754781 self .on_values_changed ()
755782
783+ def _set_ordered (self , ordered ):
784+ self .ordered_cb .setChecked (ordered )
785+ self .variable_changed .emit ()
786+
756787
757788class ContinuousVariableEditor (VariableEditor ):
758789 # TODO: enable editing of display format...
@@ -766,7 +797,7 @@ class TimeVariableEditor(VariableEditor):
766797
767798def variable_icon (var ):
768799 # type: (Variable) -> QIcon
769- if isinstance (var , Categorical ):
800+ if isinstance (var , ( Categorical , Ordered ) ):
770801 return gui .attributeIconDict [1 ]
771802 elif isinstance (var , Real ):
772803 return gui .attributeIconDict [2 ]
@@ -835,7 +866,7 @@ class OWEditDomain(widget.OWWidget):
835866 description = "Rename variables, edit categories and variable annotations."
836867 icon = "icons/EditDomain.svg"
837868 priority = 3125
838- keywords = []
869+ keywords = ["rename" , "drop" , "reorder" , "order" ]
839870
840871 class Inputs :
841872 data = Input ("Data" , Orange .data .Table )
@@ -1037,7 +1068,7 @@ def open_editor(self, index):
10371068 tr = []
10381069
10391070 editors = {
1040- Categorical : 0 ,
1071+ Categorical : 0 , Ordered : 0 ,
10411072 Real : 1 ,
10421073 Time : 2 ,
10431074 String : 3
@@ -1248,7 +1279,7 @@ def i(text):
12481279 def text (text ):
12491280 return "<span>{}</span>" .format (escape (text ))
12501281 assert trs
1251- rename = annotate = catmap = None
1282+ rename = annotate = catmap = ordered = None
12521283
12531284 for tr in trs :
12541285 if isinstance (tr , Rename ):
@@ -1257,10 +1288,17 @@ def text(text):
12571288 annotate = tr
12581289 elif isinstance (tr , CategoriesMapping ):
12591290 catmap = tr
1291+ elif isinstance (tr , ChangeOrdered ):
1292+ ordered = tr
1293+
12601294 if rename is not None :
12611295 header = "{} → {}" .format (var .name , rename .name )
12621296 else :
12631297 header = var .name
1298+ if ordered is not None and ordered .ordered != isinstance (var , Ordered ):
1299+ assert isinstance (var , (Categorical , Ordered ))
1300+ header += " (changed to {})" .format (
1301+ "ordered" if ordered .ordered else "unordered" )
12641302 values_section = None
12651303 if catmap is not None :
12661304 values_section = ("Values" , [])
@@ -1328,7 +1366,10 @@ def abstract(var):
13281366 if isinstance (var , Orange .data .DiscreteVariable ):
13291367 values , base = var .values , var .base_value
13301368 base = values [base ] if base >= 0 else None
1331- return Categorical (var .name , tuple (values ), base , annotations )
1369+ if var .ordered :
1370+ return Ordered (var .name , tuple (values ), base , annotations )
1371+ else :
1372+ return Categorical (var .name , tuple (values ), base , annotations )
13321373 elif isinstance (var , Orange .data .TimeVariable ):
13331374 return Time (var .name , annotations )
13341375 elif isinstance (var , Orange .data .ContinuousVariable ):
@@ -1360,16 +1401,20 @@ def apply_transform(var, trs):
13601401@apply_transform .register (Orange .data .DiscreteVariable )
13611402def apply_transform_discete (var , trs ):
13621403 # type: (Orange.data.DiscreteVariable, ...) -> ...
1404+ # pylint: disable=too-many-branches
13631405 name , annotations = var .name , var .attributes
13641406 base_value = var .base_value
13651407 mapping = None
1408+ ordered = var .ordered
13661409 for tr in trs :
13671410 if isinstance (tr , Rename ):
13681411 name = tr .name
13691412 elif isinstance (tr , CategoriesMapping ):
13701413 mapping = tr .mapping
13711414 elif isinstance (tr , Annotate ):
13721415 annotations = _parse_attributes (tr .annotations )
1416+ elif isinstance (tr , ChangeOrdered ):
1417+ ordered = tr .ordered
13731418
13741419 source_values = var .values
13751420 if mapping is not None :
@@ -1399,7 +1444,8 @@ def positions(values):
13991444 else :
14001445 lookup = Identity (var )
14011446 variable = Orange .data .DiscreteVariable (
1402- name , values = dest_values , base_value = base_value , compute_value = lookup
1447+ name , values = dest_values , base_value = base_value , compute_value = lookup ,
1448+ ordered = ordered ,
14031449 )
14041450 variable .attributes .update (annotations )
14051451 return variable
0 commit comments