summaryrefslogtreecommitdiff
path: root/taurus/lib/taurus/qt/qtgui/table/taurusgrid.py
blob: 5048dfab42da90baf1f486fc411fa1fd65055e3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
#!/usr/bin/env python

#############################################################################
##
## This file is part of Taurus
## 
## http://taurus-scada.org
##
## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
## 
## Taurus is free software: you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## Taurus is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU Lesser General Public License for more details.
## 
## You should have received a copy of the GNU Lesser General Public License
## along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
##
#############################################################################

"""
taurusgrid.py: module containing the Taurus Widget: TaurusGrid 
original idea and development by gcuni
integrated with taurus and regular expressions by srubio
alba, 2009
"""

__all__ = ["TaurusGrid"]

__docformat__ = 'restructuredtext'

import re
import operator
import traceback
import Queue
from functools import partial

from taurus.external.qt import Qt, QtGui, QtCore

import taurus
from taurus.qt.qtcore.util.emitter import modelSetter,TaurusEmitterThread,SingletonWorker,MethodModel
from taurus.core.taurusmanager import TaurusManager
from taurus.core.util.log import Logger
from taurus.qt.qtgui.base import TaurusBaseWidget
from taurus.qt.qtgui.panel import TaurusValue
from taurus.qt.qtgui.display import TaurusValueLabel,TaurusStateLabel
from taurus.qt.qtgui.input import TaurusValueLineEdit

metachars = re.compile('([.][*])|([.][^*])|([$^+\-?{}\[\]|()])')

def re_search_low(regexp,target): return re.search(regexp.lower(),target.lower())
def re_match_low(regexp,target): return re.match(regexp.lower(),target.lower())

def get_all_models(expressions,limit=1000):
    '''
    All devices matching expressions must be obtained.
    For each device only the good attributes are read.
    
    It practically equals to fandango.get_matching_attributes; check which is better!
    Move this method to taurus.core.tango.search 
    '''
    #print( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions))
    if isinstance(expressions,str):
        #if any(re.match(s,expressions) for s in ('\{.*\}','\(.*\)','\[.*\]')):
            ##self.debug( 'evaluating expressions ....')
            #expressions = list(eval(expressions))
        #else:
            ##self.debug( 'expressions as string separated by commas ...')
        expressions = expressions.split(',')
            
    elif any(isinstance(expressions,klass) for klass in (QtCore.QStringList,list,tuple,dict)):
        #self.debug( 'expressions converted from list ...')
        expressions = list(str(e) for e in expressions)
        
    #self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions))
    taurus_db = taurus.core.taurusmanager.TaurusManager().getFactory()().getDatabase()
    #taurus_db = taurus.Database(os.environ['TANGO_HOST'])
    if 'SimulationDatabase' in str(type(taurus_db)):
        #self.trace( 'Using a simulated database ...')
        models = expressions
    else:
        all_devs = taurus_db.get_device_exported('*')
        models = []
        for exp in expressions:
            #self.trace( 'evaluating exp = "%s"' % exp)
            exp = str(exp)
            devs = []
            targets = []
            if exp.count('/')==3: device,attribute = exp.rsplit('/',1)
            else: device,attribute = exp,'State'
            
            if any(c in device for c in '.*[]()+?'):
                if '*' in device and '.*' not in device: device = device.replace('*','.*')
                devs = [s for s in all_devs if re_match_low(device,s)]
            else:
                devs = [device]
                
            #self.trace( 'TaurusGrid.get_all_models(): devices matched by %s / %s are %d:' % (device,attribute,len(devs)))
            #self.debug( '%s' % (devs))
            for dev in devs:
                if any(c in attribute for c in '.*[]()+?'):
                    if '*' in attribute and '.*' not in attribute: attribute = attribute.replace('*','.*')
                    try:
                        #taurus_dp = taurus.core.taurusdevice.TaurusDevice(dev)
                        taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice(dev)
                        #self.debug( "taurus_dp = %s"%taurus_dp.getFullName())
                        attrs = [att.name for att in taurus_dp.attribute_list_query() if re_match_low(attribute,att.name)]
                        targets.extend(dev+'/'+att for att in attrs)
                    except Exception,e: 
                        #self.warning( 'ERROR! TaurusGrid.get_all_models(): Unable to get attributes for device %s: %s' % (dev,str(e)))
                        pass
                else: targets.append(dev+'/'+attribute)
            #print 'TaurusGrid.get_all_models(): targets added by %s are: %s' % (exp,targets)
            models.extend(targets)
    models = models[:limit]
    #print( 'Out of TaurusGrid.get_all_models(...)')
    return models   
                    
def get_readwrite_models(expressions,limit=1000):
    '''
    All devices matching expressions must be obtained.
    For each device only the good attributes are read.
    '''
    #self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions))
    if isinstance(expressions,str):
        if any(re.match(s,expressions) for s in ('\{.*\}','\(.*\)','\[.*\]')):
            #self.trace( 'evaluating expressions ....')
            expressions = list(eval(expressions))
        else:
            #self.trace( 'expressions as string separated by commas ...')
            expressions = expressions.split(',')
            
    elif any(isinstance(expressions,klass) for klass in (QtCore.QStringList,list,tuple,dict)):
        expressions = list(str(e) for e in expressions)
        
    taurus_db = taurus.core.taurusmanager.TaurusManager().getFactory()().getDatabase()
    if 'SimulationDatabase' in str(type(taurus_db)):
      models = expressions
    else:
      all_devs = taurus_db.get_device_exported('*')
      models = []
      for exp in expressions:
          exp = str(exp)
          devs = []
          targets = []
          if exp.count('/')==3: device,attribute = exp.rsplit('/',1)
          else: device,attribute = exp,'State'
          
          if any(c in device for c in '.*[]()+?'):
              if '*' in device and '.*' not in device: device = device.replace('*','.*')
              devs = [s for s in all_devs if re_match_low(device,s)]
          else:
              devs = [device]
              
          for dev in devs:
              if any(c in attribute for c in '.*[]()+?'):
                  if '*' in attribute and '.*' not in attribute: attribute = attribute.replace('*','.*')
                  try: 
                      taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice(dev)
                      attrs = [att.name for att in taurus_dp.attribute_list_query() if re_match_low(attribute,att.name) and att.isReadOnly()]
                      targets.extend(dev+'/'+att for att in attrs)
                  except Exception,e: 
                    pass
              else: targets.append(dev+'/'+attribute)
          models.extend(targets)
    models = models[:limit]
    return models

class TaurusGrid(QtGui.QFrame, TaurusBaseWidget):
    """ TaurusGrid is a Taurus widget designed to represent a set of attributes distributed in columns and rows.
    The Model will be a list with attributes or device names (for devices the State attribute will be shown).
    Each setModel(*) execution will be able to modify the attribute list.
    An example of execution:<pre>
    /usr/bin/python taurusgrid.py "model=lt.*/VC.*/.*/((C*)|(P*)|(I*))" cols=IP,CCG,PNV rows=LT01,LT02
    </pre>
    @author originally developed by gcuni, extended by srubio and sblanch
    @todo Future releases should allow a list of filters as argument
    @todo names/widgets should be accessible as a caselessdict dictionary (e.g. for adding custom context menus)
    @todo refactoring to have methods that add/remove new widgets one by one, not only the whole dictionary
    @todo _TAGS property should allow to change row/columns meaning and also add new Custom tags based on regexp
    """
    
    #---------------------------------------------------------------------------
    # Write your own code here to define the signals generated by this widget
    #
    __pyqtSignals__ = ("modelChanged(const QString &)","itemSelected(QString)")
    
    _TAGS = ['DOMAIN','FAMILY','HOST','LEVEL','CLASS','ATTRIBUTE','DEVICE']
    
    def __init__(self, parent = None, designMode = False):
        name = self.__class__.__name__
        
        self.call__init__wo_kw(QtGui.QFrame, parent)
        #self.call__init__(TaurusBaseWidget, name, parent, designMode=designMode)
        if isinstance(parent,TaurusBaseWidget): #It was needed to avoid exceptions in TaurusDesigner!
            self.call__init__(TaurusBaseWidget, name, parent, designMode=designMode)
        else:
            self.call__init__(TaurusBaseWidget, name, designMode=designMode)

        self.title = ''
        self.showLabels = True
        self.filter = ''
        self._modelNames = []
        self.row_labels = []
        self.column_labels = []
        self._widgets_list = []
        self._last_selected = None
        self._show_frames = True
        self._show_row_frame = True
        self._show_column_frame = True
        self._show_others = False
        self._show_attr_labels = True
        self._show_attr_units = True
        self.hideLabels=False
        
        self.defineStyle()
        self.modelsQueue = Queue.Queue()
        self.__modelsThread = None
        if not designMode:
            self.modelsThread

    @property
    def modelsThread(self):
        modelsThread = self.__modelsThread
        if modelsThread is None:
            modelsThread = SingletonWorker(parent=self, name='TaurusGrid',
                                           queue=self.modelsQueue,
                                           method=modelSetter, cursor=True)
            self.__modelsThread = modelsThread
        return modelsThread

    def save(self,filename):
        import pickle
        d = {
            'model':self.filter,
            'row_labels':self.row_labels,'column_labels':self.column_labels,
            'frames':self._show_row_frame or self._show_column_frame,'labels':self._show_attr_labels,
            'units':self._show_attr_units,'others':self._show_others
            }
        f = open(filename,'w')
        pickle.dump(d,f)
        f.close()
        
    def load(self,filename,delayed=False):
        self.trace('In TauGrid.load(%s,%s)'%(filename,delayed))
        if not isinstance(filename,dict):
            manual = False
            import pickle
            f = open(filename)
            d = pickle.load(f)
            f.close()
        else: 
            manual = True
            d = filename
        self.setRowLabels(d['row_labels'])
        self.setColumnLabels(d['column_labels'])  
        self.showAttributeLabels(d.get('labels',True))
        self.showAttributeUnits(d.get('units',True))
        self.showOthers(d.get('others',True))
        self.showRowFrame(d.get('frames',True))
        if manual: self.showColumnFrame(d.get('frames',True))
        self.setModel(d['model'],delayed=d.get('delayed',delayed))
        return self._modelNames 
    
    def defineStyle(self):
        """ Defines the initial style for the widget """
        #-----------------------------------------------------------------------
        # Write your own code here to set the initial style of your widget
        #
        self.setLayout(QtGui.QGridLayout())
        #self.layout().setContentsMargins(0,0,0,0) 
        self.updateStyle()
        
    def sizeHint(self):
        return QtGui.QFrame.sizeHint(self)

    def minimumSizeHint(self):
        return QtGui.QFrame.minimumSizeHint(self)
    
    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
    # TaurusBaseWidget over writing
    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
    
    def getModelClass(self):
        #-----------------------------------------------------------------------
        # [MANDATORY]
        # Replace your own code here
        # ex.: return taurus.core.taurusattribute.Attribute
        raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self)) 
        return list
    
    def attach(self):
        """Attaches the widget to the model"""
        
        if self.isAttached():
            return True
        
        #-----------------------------------------------------------------------
        # Write your own code here before attaching widget to attribute connect 
        # the proper signal so that the first event is correctly received by the
        # widget
        #
        # Typical code is:
        #self.connect(self, QtCore.SIGNAL('valueChangedDueToEvent(QString)'), 
        #             self.setTextValue)
        
        
        ret = TaurusBaseWidget.attach(self)
        
        # by default enable/disable widget according to attach state
        self.setEnabled(ret)
        return ret

    def detach(self):
        """Detaches the widget from the model"""
        
        TaurusBaseWidget.detach(self)

        #-----------------------------------------------------------------------
        # Write your own code here after detaching the widget from the model 
        #
        # Typical code is:
        #self.emit(QtCore.SIGNAL('valueChangedDueToEvent(QString)'), 
        #          QtCore.QString(value_str))
        #self.disconnect(self, QtCore.SIGNAL('valueChangedDueToEvent(QString)'),
        #                self.setTextValue)
        
        # by default disable widget when dettached
        self.setEnabled(False)
    
    #---------------------------------------------------------------------------
    # [MANDATORY]
    # Uncomment the following method if your superclass does not provide with a 
    # isReadOnly() method or if you need to change its behavior
    
    #def isReadOnly(self):
    #    return True
    
    def updateStyle(self):
        #-----------------------------------------------------------------------
        # Write your own code here to update your widget style
        self.trace('@'*80)
        self.trace('In TaurusGrid.updateStyle() ....... It seems never called!!!!')
        self.trace('@'*80)
        
        #It was showing an annoying "True" in the widget
        #value = self.getShowText() or ''
        #if self._setText: self._setText(value) ##It must be included
        
        #update tooltip
        self.setToolTip(self.getFormatedToolTip()) ##It must be included
                
        # send a repaint in the end
        if hasattr(self,'title_widget'):
            if self.title: self.title_widget.show()
            else: self.title_widget.hide()
                       
        self.update()
        
    #---------------------------------------------------------------------------
    # Write your own code here for your own widget properties
    
    def setModel(self,model,devsInRows=False,delayed=False,append=False,load=True):
        '''The model can be initialized as a list of devices or hosts or dictionary or ...'''
        #self.setModelCheck(model) ##It must be included
        #differenciate if the model is a RegExp
        if isinstance(model,dict):
            self.load(model)
        else:
            model = isinstance(model,(str,QtCore.QString)) and [model] or list(model)
            self.trace('#'*80)
            self.trace('In TaurusGrid.setModel(%s)'%str(model)[:100])
    
            self.delayed = delayed
            self.filter = model
            if any('*' in m for m in model):
                model = get_all_models(model)
                self.debug('model was a RegExp, done the query and converted to an attr list')
    
            if not self._modelNames == []:#clean to start from scratch
                for widget in self._widgets_list:
                    del widget
    
            #here we always have the reals model list, even if it comes from a regexp
            if append: self._modelNames = self._modelNames+model
            else: self._modelNames = model
            
            self.debug(('In TaurusGrid.setModel(...): modelNames are %s'%(self._modelNames))[:100]+'...')
            
            if load:
                self.trace('In TaurusGrid.setModel(%s,load=True): modelNames are %d'%(str(model)[:100]+'...',len(self._modelNames)))#,self._modelNames)) 
                if devsInRows:
                    self.setRowLabels(','.join(set(d.rsplit('/',1)[0] for d in self._modelNames)))
                self.create_widgets_table(self._modelNames)
                self.modelsQueue.put((MethodModel(self.showRowFrame),self._show_row_frame))
                self.modelsQueue.put((MethodModel(self.showColumnFrame),self._show_column_frame))
                self.modelsQueue.put((MethodModel(self.showOthers),self._show_others))
                self.modelsQueue.put((MethodModel(self.showAttributeLabels),self._show_attr_labels))
                self.modelsQueue.put((MethodModel(self.showAttributeUnits),self._show_attr_units))
                self.updateStyle()        
                        
                if not self.delayed:
                    self.trace('In setModel(): not delayed loading of models')
                    if not self.modelsThread.isRunning(): 
                        #print 'In setModel(): Starting Thread! (%d objs in queue)'%(self.modelsThread.queue.qsize())
                        self.trace('<'*80)
                        self.modelsThread.start()#self.modelsThread.IdlePriority)        
                    else:
                        #print 'In setModel(): Thread already started! (%d objs in queue)'%(self.modelsThread.queue.qsize())
                        self.modelsThread.next()
                else: 
                    self.trace('In setModel(): models loading delayed!')
                    pass
                    
            self.trace('Out of TaurusGrid.setModel(%s)'%str(model)[:100])
            self.updateStyle()
        return
    
    def getModel(self):
        return self._modelNames
    
    def resetModel(self):
        self._modelNames = []
        self.updateFromList(self._modelNames)
        return
    
   
    def setTitle(self,title):
        self.title = str(title)
        if hasattr(self,'title_widget'):
            if title: 
                self.title_widget.setText(self.title)
                self.title_widget.show()
            else: self.title_widget.hide()
    
    def parse_labels(self,text):
        if any(text.startswith(c[0]) and text.endswith(c[1]) for c in [('{','}'),('(',')'),('[',']')]):
            try:
                labels = eval(text)
                return labels
            except Exception,e:
                self.warning('ERROR! Unable to parse labels property: %s'%str(e))
                return []
        else:
            exprs = [t.strip() for t in text.split(',')]
            labels = [(':' in e and (e.split(':',1)[0].strip(),e.split(':',1)[-1].strip()) or (e,e)) for e in exprs]
            return labels
        
    def setRowLabels(self,rows):
        '''The model can be initialized as a list of devices or hosts or ...'''
        #self.setModelCheck(model) ##It must be included
        self.row_labels = self.parse_labels(str(rows))
        try:
            self.rows = [r[0] for r in self.row_labels]
            for i in range(len(self.rows)):
                section = self.rows[i]
                self.table.setVerticalHeaderItem(i,QtGui.QTableWidgetItem(section))
        except Exception,e:
            self.debug("setRowLabels(): Exception! %s"%e)
        #self.create_widgets_table(self._columnsNames)
        
    def getRowLabels(self):
        return ','.join(':'.join(c) for c in self.row_labels)
    
    def resetRowLabels(self):
        self.row_labels = []
        return       
    
    def setColumnLabels(self,columns):
        '''The model can be initialized as a list of devices or hosts or ...'''
        #self.setModelCheck(model) ##It must be included
        self.column_labels = self.parse_labels(str(columns))
        try:
            self.columns = [c[0] for c in self.column_labels]
            for i in range(len(self.columns)):
                equipment = self.columns[i]
                self.table.setHorizontalHeaderItem(i,QtGui.QTableWidgetItem(equipment))
        except Exception,e:
            self.debug("setColumnLabels(): Exception! %s"%e)
        #self.create_widgets_table(self._columnsNames)
        
    def getColumnLabels(self):
        return ','.join(':'.join(c) for c in self.column_labels)
    
    def resetColumnLabels(self):
        self.column_labels = []
        return        
    
    #FIXME: when they are called before setModel they fails because 
    # frames are not yet created, and it doesn't has memoty about this.
    def showRowFrame(self,boolean):
        self._show_row_frame = boolean
        if hasattr(self,'rows_frame'):
            if boolean:
                self.rows_frame.show()
            else:
                self.rows_frame.hide()
                
    def showColumnFrame(self,boolean):
        self._show_column_frame = boolean
        if hasattr(self,'columns_frame'):
            if boolean:
                self.columns_frame.show()
            else:
                self.columns_frame.hide()
                
    def showAttributeLabels(self,boolean):
        self.trace('In showAttributeLabels(%s)'%boolean)
        self._show_attr_labels = boolean
        for tv in self._widgets_list:
            try:
                if tv and tv.labelWidget:
                    if boolean: tv.labelWidget().show()
                    else: tv.labelWidget().hide()
            except: pass
        return self._show_attr_labels
        
    def showAttributeUnits(self,boolean):
        self.trace('In showAttributeUnits(%s)'%boolean)
        self._show_attr_units = boolean
        for tv in self._widgets_list:
            try:
                if tv and tv.unitsWidget:
                    if boolean: tv.unitsWidget().show()
                    else: tv.unitsWidget().hide()
            except: pass
        return self._show_attr_units

    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
    # QT properties 
    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-

    model = QtCore.pyqtProperty("QStringList", getModel, 
                                setModel, 
                                resetModel)
                                
    rowlabels = QtCore.pyqtProperty("QString", getRowLabels, 
                                setRowLabels, 
                                resetRowLabels)

    columnlabels = QtCore.pyqtProperty("QString", getColumnLabels, 
                                setColumnLabels, 
                                resetColumnLabels)
                                
    useParentModel = QtCore.pyqtProperty("bool",
                                         TaurusBaseWidget.getUseParentModel, 
                                         TaurusBaseWidget.setUseParentModel,
                                         TaurusBaseWidget.resetUseParentModel)

    #---------------------------------------------------------------------------
    # Write your own code here for your own widget properties
              
    def create_widgets_dict(self,models):
        from collections import defaultdict
        values = defaultdict(lambda: defaultdict(list)) #recursive dictionary with 2 levels
        #domains = list(set(m.split('/')[0].upper()))
        #families = list(set(m.split('/')[1].upper()))
        
        if not self.row_labels: #Domains used by default
            self.row_labels = sorted(list(set(m.split('/')[0].upper() for m in models if m.count('/')>=2)))
            self.row_labels = zip(self.row_labels,self.row_labels)
        if not self.column_labels: #Families used by default
            self.column_labels = sorted(list(set(m.split('/')[1].upper() for m in models if m.count('/')>=2)))
            self.column_labels = zip(self.column_labels,self.column_labels)
            
        #for m in models:
            #if m.count('/')<2:
                #self.warning('Wrong model cannot be added: %s'%m)
            #else:
                #domain,family = m.upper().split('/')[:2]
                #values[domain][family].append(m)
                
        row_not_found, col_not_found = False, False                
                
        for m in models:
            row,column = 'Others','Others'
            for label,rexp in self.row_labels:
                if '*' in rexp and '.*' not in rexp: rexp = rexp.replace('*','.*')
                if re_search_low(rexp,m):
                    row = label
                    break
            for label,rexp in self.column_labels:
                if '*' in rexp and '.*' not in rexp: rexp = rexp.replace('*','.*')
                if re_search_low(rexp,m):
                    column = label
                    break
            if 'Others' == row: row_not_found = True
            if 'Others' == column: col_not_found = True
            self.debug('Model %s added to row %s , column %s' % (m,row,column))
            values[row][column].append(m)
        if row_not_found: self.row_labels.append(('Others','.*'))
        if col_not_found: self.column_labels.append(('Others','.*'))
            
        return values
            
    def create_frame_with_gridlayout(self):
        """ Just a 'macro' to create the layouts that seem to fit better. """
        frame = QtGui.QFrame()
        frame.setLayout(QtGui.QGridLayout())
        frame.layout().setContentsMargins(2,2,2,2)
        frame.layout().setSpacing(0)
        frame.layout().setSpacing(0)
        return frame
            
    def create_widgets_table(self,models):
        
        ##Added a title to the panel
        self.title_widget = QtGui.QLabel()
        self.layout().addWidget(self.title_widget,0,0)
        self.setTitle(self.title)
        
        dct = self.create_widgets_dict(models)
        ##Assignments modified to keep the order of labels as inserted in properties
        self.rows = [r[0] for r in self.row_labels]#dct.keys()
        self.columns = [c[0] for c in self.column_labels]#list(set(reduce(operator.add,v.keys()) for v in dct.values()))
        
        values = []
        for row in self.rows:
            line = []
            for col in self.columns:
                #line.append(dct[row][col] if col in dct[row] else [])
                if col in dct[row]: line.append(dct[row][col])
                else: line.append([])
            values.append(line)
            
        ## Here is where the table is created!
        self.table = self.build_table(values)
           
        # SET COLUMN HEADERS (self.columns)
        for i in range(len(self.columns)):
            equipment = self.columns[i]
            self.table.setHorizontalHeaderItem(i,QtGui.QTableWidgetItem(equipment))
            # SOMEDAY THIS WILL BE ICONS
    
        # SET ROW HEADERS (self.rows)
        for i in range(len(self.rows)):
            section = self.rows[i]
            self.table.setVerticalHeaderItem(i,QtGui.QTableWidgetItem(section))
        
#        table.setAutoScroll(True)
#        resize_mode = QtGui.QHeaderView.Stretch
#        table.horizontalHeader().setResizeMode(resize_mode)
        #table.verticalHeader().setResizeMode(resize_mode)
        
#        for row in range(len(self.rows)):
#            table.setRowHeight(row,5+25*sum(len(dct[self.rows[row]][col]) for col in self.columns))
        #for col in range(len(self.columns)):
        #    table.setColumnWidth(col,300)        
        
        use_scroll = False # It didn't work ... it doesn't allow the table widget to resize
        if use_scroll:
            scrollable = QtGui.QScrollArea(self)
            scrollable.setWidget(self.table)
            self.layout().addWidget(scrollable,1,0)
        else: self.layout().addWidget(self.table,1,0)
        
        #-------------------------------------------------------------------------------------
        # SECTION CHECKBOXES
        self.checkboxes_frame = self.create_frame_with_gridlayout()
        
        self.rows_frame = self.create_frame_with_gridlayout()
        self.rows_frame.setFrameStyle(QtGui.QFrame.Box)
        if not self._show_row_frame: self.rows_frame.hide()
        self.checkboxes_frame.layout().addWidget(self.rows_frame,0,0)
        
        self.columns_frame = self.create_frame_with_gridlayout()
        self.columns_frame.setFrameStyle(QtGui.QFrame.Box)
        if not self._show_column_frame: self.columns_frame.hide()
        self.checkboxes_frame.layout().addWidget(self.columns_frame,0,1)
        
        layout_row = 0
        layout_col = 0
        # For all rows, create rows of three checkboxes in order to
        # show or hide the corresponding rows in the table
        for i in range(len(self.rows)):
            section = self.rows[i]
            checkbox = QtGui.QCheckBox(section)
            if checkbox.text() == 'Others':
                checkbox.setChecked(False)
                if not self._show_others: checkbox.hide()
            else:
                checkbox.setChecked(True)
            self.rows_frame.layout().addWidget(checkbox,layout_row,layout_col)
            layout_col += 1
            if layout_col == 3:
                layout_col = 0
                layout_row += 1
            QtCore.QObject.connect(checkbox,QtCore.SIGNAL('toggled(bool)'),self.show_hide_rows)
        self.show_hide_rows()
        
        layout_row = 0
        layout_col = 0
        # For all self.columns, create rows of three checkboxes in order to
        # show or hide the corresponding columns in the table
        for i in range(len(self.columns)):
            column = self.columns[i]
            checkbox = QtGui.QCheckBox(column)
            if checkbox.text() == 'Others':
                checkbox.setChecked(False)
                if not self._show_others: checkbox.hide()
            else:
                checkbox.setChecked(True)
            self.columns_frame.layout().addWidget(checkbox,layout_row,layout_col)
            layout_col += 1
            if layout_col == 3:
                layout_col = 0
                layout_row += 1
            QtCore.QObject.connect(checkbox,QtCore.SIGNAL('toggled(bool)'),self.show_hide_columns)
        self.show_hide_columns()
        
        self.layout().addWidget(self.checkboxes_frame,2,0)
        #self.resize(800,600)

    def show_hide_rows(self):
        """
        This needs refactoring to be together with the show_hide_columns method
        """
        for checkbox in self.rows_frame.children():
            if isinstance(checkbox,QtGui.QCheckBox):
                table_row = self.rows.index(checkbox.text())
                if checkbox.isChecked():
                    self.table.showRow(table_row)
                else:
                    self.table.hideRow(table_row)

    def show_hide_columns(self):
        """
        This needs refactoring to be together with the show_hide_rows method
        """
        for checkbox in self.columns_frame.children():
            if isinstance(checkbox,QtGui.QCheckBox):
                table_col = self.columns.index(checkbox.text())
                if checkbox.isChecked():
                    self.table.showColumn(table_col)
                else:
                    self.table.hideColumn(table_col)

    def showOthers(self,boolean):
        self._show_others = boolean
        if hasattr(self,'rows_frame'):
            for checkbox in self.rows_frame.children():
                if isinstance(checkbox,QtGui.QCheckBox) and checkbox.text() == 'Others':
                    if self._show_others: checkbox.show()
                    else: checkbox.hide()
        if hasattr(self,'columns_frame'):
            for checkbox in self.columns_frame.children():
                if isinstance(checkbox,QtGui.QCheckBox) and checkbox.text() == 'Others':
                    if self._show_others: checkbox.show()
                    else: checkbox.hide()

    def build_table(self,values):
        """
        This is a builder. For all the elements in widgets matrix,
        just set the corresponding cells of the QTableWidget.
        """
        self.trace('In TaurusGrid.build_table(%s)'%values)
        widgets_matrix = self.build_widgets(values,self.showLabels)
        rows = len(widgets_matrix)
        cols = rows and len(widgets_matrix[0]) or 0
        
        table = QtGui.QTableWidget()
        table.setItemDelegate(Delegate(table))
        # This example replaces the blue background of selected cells in tables
        palette = Qt.QPalette()
        palette.setBrush(palette.Active,palette.Highlight,Qt.QBrush(Qt.Qt.white))
        table.setPalette(palette)        
        
        table.setRowCount(rows)
        table.setColumnCount(cols)
        for row in range(len(widgets_matrix)):
            for col in range(len(widgets_matrix[row])):
                table.setCellWidget(row,col,widgets_matrix[row][col])
        
        #table.resizeColumnsToContents()
        #table.resizeRowsToContents()
        table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
        #table.verticalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
        #table.horizontalHeader().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        table.verticalHeader().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        
        return table

    def build_widgets(self,values,show_labels=False,width=240,height=20,value_width=120):
        widgets_matrix = []
        for row in values:
            widgets_row = []
            for cell in row:
                cell_frame = self.create_frame_with_gridlayout()
                count = 0
                for synoptic in sorted(cell):
                    self.debug("processing synoptic %s"%synoptic)
                    name = model = synoptic

                    self.debug('Creating TaurusValue with model =  %s'%model)
                    synoptic_value = TaurusValue(cell_frame)    
                    self.modelsQueue.put((synoptic_value,model))
                    QtCore.QObject.connect(synoptic_value, QtCore.SIGNAL("itemClicked(QString)"), self.itemClicked)
                            
                    if self.hideLabels:
                        synoptic_value.setLabelWidgetClass(None)
                    else: 
                        synoptic_value.setLabelConfig('label') ## DO NOT DELETE THIS LINE!!!      
                    cell_frame.layout().addWidget(synoptic_value,count,0)
                    self._widgets_list.append(synoptic_value)
                    count += 1
                    
                #Done in this way as TauValue.mousePressEvent are never called
                def mousePressEvent(event,obj):
                    #print 'In cell clicked'
                    targets = set(str(child.getModelName()) for child in obj.children() 
                        if hasattr(child,'underMouse') and child.underMouse() and hasattr(child,'getModelName'))
                    [obj.emit(Qt.SIGNAL("itemClicked(QString)"),t) for t in targets]
                
                cell_frame.mousePressEvent = partial(mousePressEvent,obj=cell_frame)
                QtCore.QObject.connect(cell_frame, QtCore.SIGNAL("itemClicked(QString)"), self.itemClicked)
                
                widgets_row.append(cell_frame)
            widgets_matrix.append(widgets_row)
        return widgets_matrix
        
    def itemClicked(self,item_name):
        self.trace('In TaurusGrid.itemClicked(%s)'%item_name)
        self.setItemSelected(item_name)
        self.emit(QtCore.SIGNAL("itemClicked(QString)"),str(item_name))
        
    def setItemSelected(self,item_name='',selected=True):
        """ it adds a blue frame around a clicked item. """
        if isinstance(item_name,TaurusValue): 
            self.trace('In TaurusGrid.setItemSelected(%s,%s)'%(str(item_name.getModel()),selected))
            item = item_name
        else: 
            self.trace('In TaurusGrid.setItemSelected(%s,%s)'%(str(item_name),selected))
            if item_name: item = self.getItemByModel(item_name)
            else: item = self._last_selected
        if item:
            if selected:
                item._labelWidget.setStyleSheet('border-style: solid ; border-width: 1px; border-color: blue; color: blue; border-radius:4px;')
                if self._last_selected and self._last_selected!=item:
                    self.setItemSelected(self._last_selected,False)
                self._last_selected = item
            else:
                item._labelWidget.setStyleSheet('border-style: solid; border-width: 1px; border-color: transparent; color: black;  border-radius:4px;')        
                self._last_selected = None
        else: return None        
    
    def getItemByModel(self, model, index=0):
        #a particular model can be many times, index means which one of them
        model = str(model).lower()
        for widget in self._widgets_list:
            if str(widget.getModel()).lower() == model:
                if index <= 0:
                    return widget
                else:
                    index -= 1

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusBaseWidget.getQtDesignerPluginInfo()
        ret['module'] = 'taurus.qt.qtgui.table'
        ret['group'] = 'Taurus Views'
        ret['icon'] = ":/designer/grid.png"
        return ret

class Delegate(QtGui.QItemDelegate):
    
    def __init__(self, parent=None):
        QtGui.QItemDelegate.__init__(self,parent)
        
    def sizeHint(self, option, index):
        table = self.parent()
        widget = table.cellWidget(index.row(),index.column())
        size = widget.sizeHint()
        return size

def sysargs_to_dict(defaults=[]):
    import sys
    i,result = 0,{}
    for a in sys.argv[1:]:
        if '=' in a:
            bar = a.split('=')
            if bar[1] in ['True','False']:#convert string to boolean
                bar[1] = eval(bar[1])
            result[bar[0].replace('--','')] = bar[1]
        else:
            result[defaults[i]] = a
            i+=1
    return result
        
        
if __name__ == '__main__':
    import sys   
    if len(sys.argv)<2: 
        print "The format of the call is something like:"
        print '\t/usr/bin/python taurusgrid.py grid.pickle.file'
        print '\t/usr/bin/python taurusgrid.py "model=lt.*/VC.*/.*/((C*)|(P*)|(I*))" cols=IP,CCG,PNV rows=LT01,LT02 others=False rowframe=True colframe=False'
        exit()
        
    app = QtGui.QApplication(sys.argv[0:1])
    gui = TaurusGrid()

    try: #first try if argument is a file to be opened
      filename = sys.argv[1]
      open(filename,'r')
      gui.load(filename)
    except:
      args = sysargs_to_dict(['model','rows','cols','others','rowframe','colframe'])
      print "args = %s"%args
      if args.get('rows'): gui.setRowLabels(args['rows'])
      if args.get('cols'): gui.setColumnLabels(args['cols'])
      if args.get('model'): gui.setModel(args['model'])
      gui.showRowFrame('rowframe' in args and args['rowframe'] and True)
      gui.showColumnFrame('colframe' in args and args['colframe'] and True)
      gui.showOthers('others' in args and args['others'] or True)

    print "current TaurusGrid model= %s"%(gui.getModel())
    gui.show()
    sys.exit(app.exec_())