#!/usr/bin/env python
#############################################################################
##
## file : db.py
##
## description : This module simplifies database access.
##
## project : Tango Control System
##
## $Author: Sergi Rubio Manrique, srubio@cells.es $
##
## $Revision: 2014 $
##
## copyleft : ALBA Synchrotron Controls Section, CELLS
## Bellaterra
## Spain
##
#############################################################################
##
## This file is part of Tango Control System
##
## Tango Control System is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as published
## by the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## Tango Control System 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <http://www.gnu.org/licenses/>.
###########################################################################
import Queue,traceback,time,sys,os
from functools import partial
import fandango
from fandango.functional import *
from fandango.log import Logger,shortstr
from fandango.dicts import SortedDict
from fandango.objects import Singleton,Decorated,Decorator,ClassDecorator,BoundDecorator
[docs]def getQt(full=False):
"""
Choosing between PyQt and Taurus Qt distributions
"""
try:
import taurus
taurus.setLogLevel(taurus.Warning)
from taurus.external.qt import Qt,QtCore,QtGui
except:
from PyQt4 import Qt,QtCore,QtGui
if full:
return Qt,QtCore,QtGui
else:
return Qt
Qt,QtCore,QtGui = getQt(True)
###############################################################################
[docs]def getStateLed(model):
from taurus.qt.qtgui.display import TaurusStateLed
led = TaurusStateLed()
led.setModel(model)
print 'In TaurusStateLed.setModel(%s)'%model
return led
[docs]def checkApplication(args=None):
try:
assert Qt.QApplication.instance(),'QApplication not running!'
return True
except:
return False
[docs]def getApplication(args=None):
app = Qt.QApplication.instance()
return app or Qt.QApplication(args or [])
[docs]def execApplication():
getApplication().exec_()
[docs]def getQwtTimeScaleDraw():
from PyQt4 import Qt,Qwt5
from PyQt4.Qwt5 import qplt,QwtScaleDraw,QwtText
class QwtTimeScale(QwtScaleDraw):
def label(self,v):
return QwtText('\n'.join(fandango.time2str(v).split()))
return QwtTimeScale()
[docs]def getRandomColor(i=None):
import random
ranges = 50,230
if i is not None:
ranges = map(int,(170,256/(i+.01),256%(i+0.01),256-256/(i+0.01)) if i<256 else (170,i/256.,i%256))
ranges = min(ranges),max(ranges)
print ranges
return Qt.QColor(
int(random.randint(*ranges)),
int(random.randint(*ranges)),
int(random.randint(*ranges)))
[docs]def getQwtPlot(series,xIsTime=False):
""" Series would be a {Label:[(time,value)]} dictionary """
app = getApplication()
from PyQt4 import Qt,Qwt5
from PyQt4.Qwt5 import qplt,QwtScaleDraw
qpt = qplt.Plot(*[qplt.Curve(
[x[0] for x in t[1]],
[y[1] for y in t[1]],t[0],
qplt.Pen(getRandomColor(i=None),2))
for i,t in enumerate(series.items())])
if xIsTime: qpt.setAxisScaleDraw(qpt.xBottom,getQwtTimeScaleDraw())
return qpt
[docs]class QOptionDialog(QDialogWidget):
"""
This class provides a fast way to update a dictionary from a Qt dialog
s = fandango.Struct(name='test',value=7.8)
qo = QOptionDialog(model=s,cast=s.cast,title='Options')
qo.show()
#Edit and save the values
s.value : 5.0
Using plain dicts:
d = {'name':'A','value':4}
qo = QOptionDialog(model=d,cast=fandango.str2type,title='Options')
qo.exec()
"""
def __init__(self,parent=None,model={},title='',cast=None):
QDialogWidget.__init__(self,parent,buttons=True)
self.accepted = False
self.cast = cast
if model: self.setModel(model)
if title: self.setWindowTitle(title)
self.connect(self,Qt.SIGNAL('accepted'),self.close)
[docs] def setModel(self,model):
self._model = model
self._widget = Qt.QWidget()
self._widget.setLayout(Qt.QGridLayout())
self._edits = {}
for i,t in enumerate(self._model.items()):
k,w = str(t[0]),Qt.QLineEdit(str(t[1]))
self._widget.layout().addWidget(Qt.QLabel(k),i,0,1,1)
self._edits[k] = w
self._widget.layout().addWidget(w,i,1,1,1)
self.setWidget(self._widget)
[docs] def accept(self):
self.accepted = True
for k,w in self._edits.items():
v = str(w.text())
self._model[k] = (self.cast or str)(v)
QDialogWidget.accept(self)
[docs] def getModel(self):
return self._model
[docs]class QExceptionMessage(object):
def __init__(self,*args):
import traceback
self.message = '\n'.join(map(str,args)) or ''
if not self.message or len(args)<2:
self.message+=traceback.format_exc()
self.qmb = Qt.QMessageBox(Qt.QMessageBox.Warning,"Exception","The following exception occurred:\n\n%s"%self.message,Qt.QMessageBox.Ok)
print 'fandango.qt.QExceptionMessage(%s)'%self.message
self.qmb.exec_()
[docs]class QColorDictionary(SortedDict,Singleton):
"""
Returns a {name:QColor} dictionary;
if sorted tries to sort by color difference
"""
def __init__(self,sort = False):
SortedDict.__init__(self)
colors = {}
last = SortedDict()
for n in Qt.QColor.colorNames():
c = Qt.QColor(n)
rgb = c.getRgb()[:3]
if not sum(rgb): last['black'] = c
elif sum(rgb)==255*3: last['white'] = c
elif not any(v.getRgb()[:3]==c.getRgb()[:3] for v in colors.values()):
colors[n] = c
else: last[n] = c
if sorted:
self.distances = {}
dark = [(n,colors.pop(n)) for n in colors.keys() if max(colors[n].getRgb()[:3])<.25*(255*3)]
light = [(n,colors.pop(n)) for n in colors.keys() if min(colors[n].getRgb()[:3])>.75*(255*3)]
self.update(
sorted(
((n,colors.pop(n)) for n in colors.keys() if all(c in (0,255) for c in colors[n].getRgb()[:3])),
key=(lambda t:sum(t[1].getRgb()[:3]))
)
)
#self.update([(n,colors.pop(n)) for n in colors.keys() if all(c in colors[n].getRgb()[:3] for c in (0,255))])
#for inc in range(0,255/2):
#self.update([(n,colors.pop(n)) for n in colors.keys() if any(c in colors[n].getRgb() for c in (0+inc,255-inc))])
#if not colors: break
#for n,color in sorted(colors,key=lambda t: -self.get_diff_code(t[1])): self[n] = color
x = len(colors)
for i in range(x):
n = sorted(colors.items(),key=lambda t: -self.get_diff_code(*t))[0][0]
self[n] = colors.pop(n)
self.update(light)
self.update(dark)
self.update(last)
else:
self.update(sorted(colors.items()))
self.update(last)
[docs] def show(self):
getApplication()
self.widget = Qt.QScrollArea()
w = Qt.QWidget()
w.setLayout(Qt.QVBoxLayout())
for n,c in self.items():
l = Qt.QLabel('%s: %s, %s'%(n,str(c.getRgb()[:3]),self.distances.get(n,None)))
l.setStyleSheet('QLabel { background-color: rgba(%s) }'%(','.join(str(x) for x in c.getRgb())))
w.layout().addWidget(l)
self.widget.setWidget(w)
self.widget.show()
[docs] def get_rgb_normalized(self,color):
rgb = color.getRgb()[:3]
average = fandango.avg(rgb)
return tuple((c-average) for c in rgb)
[docs] def get_diff_code(self,name,color):
#get_avg_diff = lambda seq: (abs(seq[0]-seq[1])+abs(seq[1]-seq[2])+abs(seq[2]-seq[0]))/3.
#return get_avg_diff(color.getRgb())
NValues = 15
MinDiff = 50
self.distances[name] = [sum((abs(x-y) if abs(x-y)>MinDiff else 0)
#for x,y in zip(self.get_rgb_normalized(color),self.get_rgb_normalized(k)))
for x,y in zip(color.getRgb()[:3],k.getRgb()[:3]))
for k in self.values()[-NValues:]]
return sum(self.distances[name]) if not any(d<MinDiff*1.5 for d in self.distances[name]) else 0
[docs]class TauFakeEventReceiver():
[docs] def event_received(self,source,type_,value):
print '%s: Event from %s: %s(%s)'% (time.ctime(),source,type_,shortstr(getattr(value,'value',value)))
[docs]class TaurusImportException(Exception):
pass
try:
import taurus.core,taurus.qt
import taurus.qt.qtgui.util.tauruscolor as colors
import taurus.qt.qtgui.base as taurusbase
from taurus.qt.qtgui.base import TaurusBaseComponent
from taurus.core import TaurusEventType,TaurusAttribute
from taurus.qt.qtgui.graphic import TaurusGraphicsItem
from taurus.qt.qtgui.container.tauruswidget import TaurusWidget
except:
print 'Unable to import Taurus!'
taurus,colors,taurusbase,tie = None,None,None,TaurusImportException
TaurusBaseComponent,TaurusEventType,TaurusAttribute,TaurusGraphicsItem,TaurusWidget = tie,tie,tie,tie,tie
[docs]def getColorsForValue(value,palette = getattr(colors,'QT_DEVICE_STATE_PALETTE',None)):
"""
Get QColor equivalent for a given Tango attribute value
It returns a Background,Foreground tuple
"""
print 'In getColorsForValue(%s)'%value
if value is None:
return Qt.QColor(Qt.Qt.gray),Qt.QColor(Qt.Qt.black)
elif hasattr(value,'value'): #isinstance(value,PyTango.DeviceAttribute):
if value.type == PyTango.ArgType.DevState:
bg_brush, fg_brush = colors.QT_DEVICE_STATE_PALETTE.qbrush(value.value)
elif value.type == PyTango.ArgType.DevBoolean:
bg_brush, fg_brush = colors.QT_DEVICE_STATE_PALETTE.qbrush((PyTango.DevState.FAULT,PyTango.DevState.ON)[value.value])
else:
bg_brush, fg_brush = colors.QT_ATTRIBUTE_QUALITY_PALETTE.qbrush(value.quality)
else:
bg_brush, fg_brush = palette.qbrush(int(value))
return bg_brush.color(),fg_brush.color()
[docs]class TauColorComponent(TaurusBaseComponent):
"""
Abstract class for Tau color-based items.
:param: defaults will be a tuple with the default foreground,background colors (appliable if value not readable)
It may allow to differentiate not-read from UNKNOWN
The TauColorComponent contains a QObject to emit Qt.SIGNALs if needed:
self.emitter.connect(self.emitter,Qt.SIGNAL("setColors"),self.parent.tabBar().setTabTextColor)
self.emitter.emit(Qt.SIGNAL("setColors"),self.getIndex(),color)
"""
def __init__(self, name = None, parent = None, defaults = None):
self.__name = name
self.call__init__(taurusbase.TaurusBaseComponent, name, parent)
self.__value = None
self._defBgColor = defaults and defaults[0] or Qt.QColor(Qt.Qt.gray)
self._defFgColor = defaults and defaults[-1] or Qt.QColor(Qt.Qt.black)
self._currBgColor,self._currFgColor = self._defBgColor,self._defFgColor
self.emitter = Qt.QObject(parent)
[docs] def setColors(self,background,foreground):
raise Exception('Method should be overriden in %s subclass!'%type(self))
#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
# Mandatory methods to be implemented in any subclass of TauComponent
#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
[docs] def setModel(self,model):
print '#'*80
self.info('In %s.setModel(%s)'%(type(self).__name__,model))
model = str(model)
self.__name = model
#If the model is a device the color will depend of its State
if model.count('/')==2: model += '/state'
#We don't want the ._name to be overriden by ._name+/state
taurusbase.TaurusBaseComponent.setModel(self,model)
[docs] def getParentTauComponent(self):
""" Returns a parent Tau component or None if no parent TaurusBaseComponent
is found."""
p = self.parentItem()
while p and not isinstance(p, TauGraphicsItem):
p = self.parentItem()
return p
#def fireEvent(self, evt_src = None, evt_type = None, evt_value = None):
[docs] def handleEvent(self, evt_src = None, evt_type = None, evt_value = None):
"""fires a value changed event to all listeners"""
self.info('In TauColorComponent(%s).handleEvent(%s,%s)'%(self.__name,evt_src,evt_type))
if evt_type!=TaurusEventType.Config:
#@todo ; added to bypass a problem with getModelValueObj()
self.__value = evt_value if evt_type!=TaurusEventType.Error else None
self.updateStyle()
[docs] def updateStyle(self):
""" Method called when the component detects an event that triggers a change
in the style.
If the value is not parseable the default colors will be set.
"""
try:
self._currBgColor,self._currFgColor = self._defBgColor,self._defFgColor
#@todo ; added to bypass a problem with getModelValueObj()
value = self.__value #self.getModelValueObj()
if value is not None:
value = value.value
self.info('In TauColorComponent(%s).updateStyle(%s)'%(self.__name,value))
self._currBgColor,self._currFgColor = getColorsForValue(value)
else:
self.info('In TauColorComponent(%s).updateStyle(%s), using defaults'%(self.__name,value))
except:
self.info('TauColorComponent(%s).updateStyle(): Unable to getColorsForValue(%s):\n%s'%(self.__name,value,traceback.format_exc()))
self.setColors(self._currBgColor,self._currFgColor)
return
[docs] def isReadOnly(self):
return True
def __str__(self):
return self.log_name + "(" + self.modelName + ")"
[docs] def getModelClass(self):
return TaurusAttribute
[docs]class QSignalHook(Qt.QObject):
"""
Class initialized with a transformation function.
For every setModel(model) it will emit a modelChanged(function(model))
"""
#__pyqtSignals__ = ("modelChanged",)
def __init__(self,function):
Qt.QObject.__init__(self)
self._model = None
self._new_model = None
self.function = function
[docs] def setModel(self,model):
model = str(model) #Do a copy or die!
from fandango import FakeLogger as FL
fl = FL('QSignalHook',True)
try:
#fl.warning('QSignalHook(%s)'%(self._model,))
self._model = model
self._new_model = self.function(model) if self.function is not None else model
#fl.warning('QSignalHook.modelChanged(%s => %s)'%(self._model,self._new_model))
self.emit(Qt.SIGNAL("modelChanged"), self._new_model)
except:
fl.warning('QSignalHook: %s'%(traceback.format_exc()))
import threading
[docs]class ModelRefresher(threading.Thread,fandango.objects.SingletonMap):
def __init__(self,period=30):
threading.Thread.__init__(self)
self.period = period
self.targets = []
self.stop_event = threading.Event()
print('ModelRefresher.init()')
[docs] def run(self):
while not self.stop_event.isSet():
self.targets = self.get_targets()
for t in self.targets:
self.fire_targets([t])
self.stop_event.wait(float(self.period)/len(self.targets))
if self.stop_event.isSet(): break
print('Model Refresh finished')
[docs] def stop(self):
self.stop_event.set()
self.join()
[docs] def get_targets(self,widgets=[]):
print('ModelRefresher.getTargets()')
widgets = widgets or getApplication().allWidgets()
targets = []
for w in widgets:
try:
if hasattr(w,'getModelObj') and w.getModelObj():
targets.append(w)
except:
pass
try:
if hasattr(w,'getCurveNames'):
for ts in w.getCurveNames():
ts = w.getCurve(ts)
if ts.getModelObj():
targets.append(ts)
if hasattr(w,'getTrendSetNames'):
for ts in w.getTrendSetNames():
ts = w.getTrendSet(ts)
if ts.getModelObj():
targets.append(ts)
except:
print w,w.getModel()
traceback.print_exc()
self.targets = targets
return targets
[docs] def fire_targets(self,targets=[]):
targets = targets or self.targets
for ts in targets:
try:
ts.fireEvent(ts,taurus.core.TaurusEventType.Change,ts.getModelValueObj(cache=False))
except:
print ts
traceback.print_exc()
return
#############################################################################################
## DO NOT EDIT THIS CLASS HERE, THE ORIGINAL ONE IS IN taurus utils emitter!!!
#############################################################################################
[docs]def modelSetter(obj,model):
if hasattr(obj,'setModel') and model:
obj.setModel(model)
return
[docs]class QWorker(Qt.QThread):
"""
IF YOU USE TAURUS, GO THERE INSTEAD: taurus.qt.qtcore.util.emitter.SingletonWorker ; it is more optimized and maintained
This object get items from a python Queue and performs a thread safe operation on them.
It is useful to delay signals in a background thread.
:param parent: a Qt/Tau object
:param name: identifies object logs
:param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used
:param method: the method to be executed using each queue item as argument
:param cursor: if True or QCursor a custom cursor is set while the Queue is not empty
USAGE
-----
Delaying model subscription using TauEmitterThread[edit]::
<pre>
#Applying TauEmitterThread to an existing class:
import Queue
from functools import partial
def modelSetter(args):
obj,model = args[0],args[1]
obj.setModel(model)
klass TauGrid(Qt.QFrame, TaurusBaseWidget):
...
def __init__(self, parent = None, designMode = False):
...
self.modelsQueue = Queue.Queue()
self.modelsThread = TauEmitterThread(parent=self,queue=self.modelsQueue,method=modelSetter )
...
def build_widgets(...):
...
previous,synoptic_value = synoptic_value,TauValue(cell_frame)
#synoptic_value.setModel(model)
self.modelsQueue.put((synoptic_value,model))
...
def setModel(self,model):
...
if hasattr(self,'modelsThread') and not self.modelsThread.isRunning():
self.modelsThread.start()
elif self.modelsQueue.qsize():
self.modelsThread.next()
...
</pre>
"""
def __init__(self, parent=None,name='',queue=None,method=None,cursor=None,sleep=5000):
"""
Parent most not be None and must be a TauGraphicsScene!
"""
Qt.QThread.__init__(self, parent)
self.name = name
self.log = Logger('TauEmitterThread(%s)'%self.name)
self.log.setLogLevel(self.log.Info)
self.queue = queue or Queue.Queue()
self.todo = Queue.Queue()
self.method = method
self.cursor = Qt.QCursor(Qt.Qt.WaitCursor) if cursor is True else cursor
self._cursor = False
self.sleep = sleep
self.emitter = Qt.QObject()
self.emitter.moveToThread(Qt.QApplication.instance().thread())
self.emitter.setParent(Qt.QApplication.instance()) #Mandatory!!! if parent is set before changing thread it could lead to segFaults!
self._done = 0
#Moved to the end to prevent segfaults ...
Qt.QObject.connect(self.emitter, Qt.SIGNAL("doSomething"), self._doSomething)
Qt.QObject.connect(self.emitter, Qt.SIGNAL("somethingDone"), self.next)
[docs] def getQueue(self):
if self.queue: return self.queue
elif hasattr(self.parent(),'getQueue'): self.parent().getQueue()
else: return None
[docs] def getDone(self):
""" Returns % of done tasks in 0-1 range """
return float(self._done)/(self._done+self.getQueue().qsize()) if self._done else 0.
def _doSomething(self,args):
self.log.debug('At TauEmitterThread._doSomething(%s)'%str(args))
if not self.method:
if isSequence(args): method,args = args[0],args[1:]
else: method,args = args,[]
else:
method = self.method
if method:
try:
method(*args)
except:
self.log.error('At TauEmitterThread._doSomething(): %s' % traceback.format_exc())
self.emitter.emit(Qt.SIGNAL("somethingDone"))
self._done += 1
return
[docs] def put(self,*args):
"""
QWorker.put(callable,arg0,arg1,arg2,...)
"""
if len(args)==1 and isSequence(args[0]): args = args[0]
self.getQueue().put(args)
[docs] def next(self):
queue = self.getQueue()
(queue.empty() and self.log.info or self.log.debug)('At TauEmitterThread.next(), %d items remaining.' % queue.qsize())
try:
if not queue.empty():
if not self._cursor and self.cursor is not None:
Qt.QApplication.instance().setOverrideCursor(Qt.QCursor(self.cursor))
self._cursor=True
item = queue.get(False) #A blocking get here would hang the GUIs!!!
self.todo.put(item)
self.log.debug('Item added to todo queue: %s' % str(item))
else:
self._done = 0.
if self._cursor:
Qt.QApplication.instance().restoreOverrideCursor()
self._cursor = False
except Queue.Empty,e:
self.log.warning(traceback.format_exc())
pass
except:
self.log.warning(traceback.format_exc())
return
[docs] def run(self):
Qt.QApplication.instance().thread().wait(self.sleep)
print '#'*80
self.log.info('At TauEmitterThread.run()')
self.next()
while True:
item = self.todo.get(True)
if isString(item):
if item == "exit":
break
else:
continue
self.log.debug('Emitting doSomething signal ...')
self.emitter.emit(Qt.SIGNAL("doSomething"), item)
#End of while
self.log.info('#'*80+'\nOut of TauEmitterThread.run()'+'\n'+'#'*80)
#End of Thread
TauEmitterThread = QWorker #For backwards compatibility
###############################################################################
###############################################################################
# QT Helping Classes
TEXT_MIME_TYPE = 'text/plain'
try:
import taurus, taurus.qt, taurus.qt.qtcore
from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, TAURUS_MODEL_LIST_MIME_TYPE
except:
print('WARNING: fandango.qt: Importing taurus.qt.qtcore.mimetypes failed!')
TAURUS_ATTR_MIME_TYPE = TAURUS_DEV_MIME_TYPE = TAURUS_MODEL_MIME_TYPE = TAURUS_MODEL_LIST_MIME_TYPE = TEXT_MIME_TYPE
@ClassDecorator
def Dropable(QtKlass):
"""
This decorator enables any Qt class to accept drops
"""
class DropableQtKlass(QtKlass): #,Decorated):
def checkDropSupport(self):
''' Initializes DropEvent support '''
try:
self.setAcceptDrops(True)
if not hasattr(self,'TAURUS_DEV_MIME_TYPE'):
self.TAURUS_DEV_MIME_TYPE = TAURUS_DEV_MIME_TYPE
self.TAURUS_ATTR_MIME_TYPE = TAURUS_ATTR_MIME_TYPE
self.TAURUS_MODEL_MIME_TYPE = TAURUS_MODEL_MIME_TYPE
self.TAURUS_MODEL_LIST_MIME_TYPE = TAURUS_MODEL_LIST_MIME_TYPE
except: traceback.print_exc()
self.TEXT_MIME_TYPE = 'text/plain'
return True
def getSupportedMimeTypes(self):
'''
:param mimetypes: (list<str>) list (ordered by priority) of MIME type names or a {mimeType: callback} dictionary (w/out priority if not using SortedDict)
'''
self.checkDropSupport()
if not getattr(self,'_supportedMimeTypes',[]):
try:
self.setSupportedMimeTypes([self.TAURUS_DEV_MIME_TYPE, self.TAURUS_ATTR_MIME_TYPE,self.TAURUS_MODEL_MIME_TYPE, self.TAURUS_MODEL_LIST_MIME_TYPE, self.TEXT_MIME_TYPE])
except:
print 'Unable to import TAURUS MIME TYPES: %s'%traceback.format_exc()
self.setSupportedMimeTypes([self.TEXT_MIME_TYPE])
return self._supportedMimeTypes
def setSupportedMimeTypes(self, mimetypes):
''' sets the mimeTypes that this widget support
:param mimetypes: (list<str>) list (ordered by priority) of MIME type names or a {mimeType: callback} dictionary (w/out priority if not using SortedDict)
'''
print 'In setSupportedMimeTypes()'
self.checkDropSupport()
self._supportedMimeTypes = mimetypes
def setDropEventCallback(self,callback):
''' Assings default DropEventCallback '''
self._dropeventcallback = callback
def getDropEventCallback(self,mimetype = None):
try:
mimes = self.getSupportedMimeTypes()
if mimetype and fandango.isMapping(mimes):
return mimes.get(mimetype)
except: traceback.print_exc()
return getattr(self,'_dropeventcallback',None) or getattr(self,'setText',None)
# ENABLING DROP OF DEVICE NAMES :
def checkSupportedMimeType(self,event,accept=False):
for t in self.getSupportedMimeTypes():
if t in event.mimeData().formats():
if accept: event.acceptProposedAction()
return self.getDropEventCallback(t) or True
return False
def dragEnterEvent(self,event): self.checkSupportedMimeType(event,accept=True)
def dragMoveEvent(self,event): event.acceptProposedAction()
def dropEvent(self, event):
'''reimplemented to support drag&drop of models. See :class:`QWidget`'''
print('dropEvent(%s): %s,%s'%(event,event.mimeData(),event.mimeData().formats()))
if event.source() is self:
print('Internal drag/drop not allowed')
mtype = self.handleMimeData(event.mimeData())
event.acceptProposedAction()
def handleMimeData(self, mimeData, method=None):
'''Selects the most appropriate data from the given mimeData object (in the order returned by :meth:`getSupportedMimeTypes`) and passes it to the given method.
:param mimeData: (QMimeData) the MIME data object from which the model is to be extracted
:param method: (callable<str>) a method that accepts a string as argument. This method will be called with the data from the mimeData object
:return: (str or None) returns the MimeType used if the model was successfully set, or None if the model could not be set
'''
for mtype in self.getSupportedMimeTypes():
try:
data = str(mimeData.data(mtype) or '')
try:
if data.strip():
try:
(method or self.getDropEventCallback(mtype))(data)
return mtype
except:
print('Invalid data (%s,%s) for MIMETYPE=%s'%(repr(mimeData),repr(data), repr(mtype)))
traceback.print_exc()
return None
except: self.info(traceback.warning_exc())
except: self.debug('\tNot dropable data (%s)'%(data))
return DropableQtKlass
QDropable = Dropable(object)
@ClassDecorator
def DoubleClickable(QtKlass):
"""
This decorator enables a Qt class to execute a 'hook' method every time is double-clicked
"""
class DoubleClickableQtKlass(QtKlass): #,Decorated):
__doc__ = DoubleClickable.__doc__
def __init__(self,*args):
self.my_hook = None
QtKlass.__init__(self,*args)
def setClickHook(self,hook):
""" the hook must be a function or callable """
self._doubleclickhook = hook #self.onEdit
def mouseDoubleClickEvent(self,event):
if getattr(self,'_doubleclickhook',None) is not None:
self._doubleclickhook()
else:
try: QtKlass.mouseDoubleClickEvent(self)
except: pass
return DoubleClickableQtKlass
QDoubleClickable = DoubleClickable(object)
@ClassDecorator
def Draggable(QtKlass):
"""
This ClassDecorator enables Drag on widgets that does not support it.
BUT!, it will fail on those that already implement it (e.g. QTextEdit)
"""
class DraggableQtKlass(QtKlass): #,Decorated):
__doc__ = Draggable.__doc__
def setDragEventCallback(self,hook,Mimetype=None):
self._drageventcallback = hook
self.Mimetype = Mimetype
def getDragEventCallback(self):
if not getattr(self,'_drageventcallback',None):
self.setDragEventCallback(lambda s=self:str(s.text() if hasattr(s,'text') else ''))
return self._drageventcallback
def mousePressEvent(self, event):
'''reimplemented to provide drag events'''
#QtKlass.mousePressEvent(self, event)
QtKlass.mousePressEvent(self,event)
print 'In Draggable(%s).mousePressEvent'%type(self)
if event.button() == Qt.Qt.LeftButton: self.dragStartPosition = event.pos()
def mouseMoveEvent(self, event):
'''reimplemented to provide drag events'''
QtKlass.mouseMoveEvent(self,event)
if not event.buttons() & Qt.Qt.LeftButton:
return
call_back = self.getDragEventCallback()
mimeData = call_back()
if not isinstance(mimeData,Qt.QMimeData):
txt = str(mimeData)
mimeData = Qt.QMimeData()
if getattr(self,'Mimetype',None):
mimeData.setData(self.Mimetype, txt)
mimeData.setText(txt) #Order is not trivial, preferred must go first
drag = Qt.QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(event.pos() - self.rect().topLeft())
dropAction = drag.start(Qt.Qt.CopyAction)
return DraggableQtKlass
QDraggable = Draggable(object)
QDraggableLabel = Draggable(Qt.QLabel)
@ClassDecorator
def MenuContexted(QtKlass):
"""
This class decorator provides a simple hook to add context menu options/callables
Example:
label = MenuContexted(Qt.QLabel)('a/device/name')
label.setContextCallbacks({'Test Device',lambda:show_device_panel('a/device/name')})
"""
class MenuContextedQtKlass(QtKlass):
__doc__ = MenuContexted.__doc__
def setContextCallbacks(self,hook_dict):
self._actions = hook_dict
def mousePressEvent(self, event):
point = event.pos()
widget = Qt.QApplication.instance().widgetAt(self.mapToGlobal(point))
# If the widget is a container:
if hasattr(widget,'_actions'):
#print('onMouseEvent(%s)'%(getattr(widget,'text',lambda:widget)()))
self._current_item = widget
if event.button()==Qt.Qt.RightButton:
self.onContextMenu(point)
elif hasattr(self,'_actions'):
self.onContextMenu()
getattr(super(type(self),self),'mousePressEvent',lambda e:None)(event)
def getContextItem(self):
return getattr(self,'_current_item',None)
def onContextMenu(self, point=None):
try:
self._contextmenu = Qt.QMenu()
for k,v in self._actions.items():
self._contextmenu.addAction(Qt.QIcon(),k,v)
if point:
self._contextmenu.exec_(self.mapToGlobal(point))
else:
self._contextmenu.exec_()
except:
traceback.print_exc()
return MenuContextedQtKlass
#class QDraggableLabel(!Draggable,Qt.QLabel):
#def __init__(self,parent=None,text=''):
#Qt.QLabel.__init__(self,text)
#Draggable.__init__(self)
[docs]class QOptionChooser(Qt.QDialog):
"""
Dialog used to trigger several options from a bash launch script
"""
def __init__(self,title,text,command,args,parent=None):
Qt.QDialog.__init__(self,parent)
self.command = command
self.args = args
self.combo = Qt.QComboBox()
self.combo.addItems(self.args)
self.buttons = Qt.QDialogButtonBox(Qt.QDialogButtonBox.Ok|Qt.QDialogButtonBox.Cancel)
self.connect(self.buttons,Qt.SIGNAL('accepted()'),self.launch)
self.connect(self.buttons,Qt.SIGNAL('rejected()'),self.close)
self.setLayout(Qt.QHBoxLayout())
self.setWindowTitle(title)
self.layout().addWidget(Qt.QLabel(text))
self.layout().addWidget(self.combo)
self.layout().addWidget(self.buttons)
[docs] def launch(self):
cmd = ' '.join(map(str,[self.command[0],self.combo.currentText()]+self.command[1:]))+' &'
print cmd
import os
os.system(cmd)
[docs]class TangoHostChooser(Qt.QDialog):
"""
Allows to choose a tango_host from a list
"""
def __init__(self,hosts):
self.hosts = sorted(hosts)
Qt.QDialog.__init__(self,None)
self.setLayout(Qt.QVBoxLayout())
self.layout().addWidget(Qt.QLabel('Choose your TangoHost:'))
self.chooser = Qt.QComboBox()
self.chooser.addItems(self.hosts)
self.layout().addWidget(self.chooser)
self.button = Qt.QPushButton('Done')
self.layout().addWidget(self.button)
self.button.connect(self.button,Qt.SIGNAL('pressed()'),self.done)
self.button.connect(self.button,Qt.SIGNAL('pressed()'),self.close)
[docs] def done(self,*args):
import os
os.environ['TANGO_HOST']=str(self.chooser.currentText())
new_value = os.getenv('TANGO_HOST')
self.close()
self.hide()
@staticmethod
[docs] def main(args=[]):
app = getApplication()
thc = TangoHostChooser(args or sys.argv[1:])
thc.show()
app.exec_()
v = str(thc.chooser.currentText())
print(v)
return v
###############################################################################
[docs]class DialogCloser(object):
"""
This decorator triggers dialog closure at the end of the decorated method
e.g.
dialog = QTextBuffer()
widget = TaurusTrend()
TaurusTrend.closeEvent = DialogCloser(d)
"""
def __init__(self,dialog):
self.dialog = dialog
def __call__(self,f):
def wrapped_closeEvent(*args):
f(*args)
try: self.dialog.close()
except:pass
return wrapped_closeEvent
[docs]def setDialogCloser(dialog,widget):
"""
set dialog to be closed on widget.closeEvent()
"""
widget.closeEvent = DialogCloser(dialog)(widget.closeEvent)
widget.hideEvent = DialogCloser(dialog)(widget.hideEvent)
[docs]def QConfirmAction(action,parent=None,title='WARNING',message='Are you sure?',options=Qt.QMessageBox.Ok|Qt.QMessageBox.Cancel):
"""
This method will just execute action but preceeded by a confirmation dialog.
To pass arguments to your action just use partial(action,*args,**kwargs) when declaring it
e.g:
self._clearbutton.connect(self._clearbutton,Qt.SIGNAL('clicked()'),fandango.partial(fandango.qt.QConfirmAction,self.clearBuffers)
"""
if Qt.QMessageBox.Ok == QtGui.QMessageBox.warning(parent,title,message,QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel):
action()
[docs]class QTextBuffer(Qt.QDialog):
"""
This dialog provides a Text dialog where logs can be inserted from your application in a round buffer.
It also provides a button to save the logs into a file if needed.
"""
def __init__(self,title='TextBuffer',maxlen=1000,parent=None):
Qt.QDialog.__init__(self,parent) #,*args)
self.setWindowTitle(title)
lwidget,lcheck = Qt.QVBoxLayout(),Qt.QHBoxLayout()
self.setLayout(lwidget)
self._maxlen = maxlen
self._buffer = [] #collections.deque could be used instead
self._count = Qt.QLabel('0/%d'%maxlen)
lwidget.addWidget(self._count)
self._browser = Qt.QTextBrowser()
lwidget.addWidget(self._browser)
self._cb = Qt.QCheckBox('Dont popup logs anymore')
self._checked = False
#self._label = Qt.QLabel('Dont popup logs anymore')
#self._label.setAlignment(Qt.Qt.AlignLeft)
map(lcheck.addWidget,(self._cb,)) #self._label))
lwidget.addLayout(lcheck)
self.connect(self._cb,Qt.SIGNAL('toggled(bool)'),self.toggle)
self._savebutton = Qt.QPushButton('Save Logs to File')
self._savebutton.connect(self._savebutton,Qt.SIGNAL('clicked()'),self.saveLogs)
self.layout().addWidget(self._savebutton)
[docs] def append(self,text):
if self._buffer and text==self._buffer[-1]: test = '+1'
self._buffer.append(text)
if len(self._buffer)>=1.2*self._maxlen:
self._buffer = self._buffer[-self._maxlen:]
self._browser.setText('\n'.join(self._buffer))
else:
self._browser.append(text)
self._count.setText('%d/%d'%(min((self._maxlen,len(self._buffer))),self._maxlen))
if not self._checked:
self.show()
[docs] def text(self):
return self._browser.toPlainText()
[docs] def setText(self,text):
self._buffer = text.split('\n')
self._browser.setText(text)
[docs] def clear(self):
self._browser.clear()
self._buffer = []
[docs] def toggle(self,arg=None):
if arg is None:
self._checked = self._cb.isChecked()
elif arg:
self._cb.setChecked(True)
self._checked = True
else:
self._cb.setChecked(False)
self._checked = False
#print 'toggled(%s): %s'%(arg,self._checked)
#sys.stdout.flush()
[docs] def saveLogs(self):
fname = str(Qt.QFileDialog.getSaveFileName(None,'Choose a file to save'))
#self.info('Saving logs to %s'%fname)
if fname: open(fname,'w').write(str(self.text()))
[docs]class QDropTextEdit(Qt.QTextEdit):
"""
This method provides a widget that allows drops of text from other widgets.
As a bonus, it provides a simple hook for DoubleClick events
But!, QTextEdit already supported drag/drop, so it is just an exercise of how to do these things.
"""
def __init__(self,*args):#,**kwargs):
self.double_click_hook = None
QtGui.QTextEdit.__init__(self,*args)#,**kwargs)
self.mimeTypes() #Default mimeTypes loaded
[docs] def setClickHook(self,hook):
""" the hook must be a function or callable """
self.double_click_hook = hook #self.onEdit
[docs] def mouseDoubleClickEvent(self,event):
if self.double_click_hook is not None:
self.double_click_hook()
else:
try: Qt.QTextEdit.mouseDoubleClickEvent(self)
except: pass
[docs] def setSupportedMimeTypes(self, mimetypes):
'''
sets the mimeTypes that this widget support
:param mimetypes: (list<str>) list (ordered by priority) of MIME type names
'''
self._supportedMimeTypes = mimetypes
[docs] def mimeTypes(self):
try:
import taurus
import taurus.qt
import taurus.qt.qtcore
from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, TAURUS_MODEL_LIST_MIME_TYPE
self.setSupportedMimeTypes([
TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_ATTR_MIME_TYPE,
TAURUS_MODEL_MIME_TYPE, 'text/plain'])
except:
import traceback
print traceback.format_exc()
print 'Unable to import TAURUS MIME TYPES'
return self.getSupportedMimeTypes()
[docs] def getSupportedMimeTypes(self):
return self._supportedMimeTypes
[docs] def addModels(self,models):
self.setText(str(self.toPlainText())+'\n'+str(models))
#In this method is where dropped data is checked
[docs] def dropEvent(self, event):
'''reimplemented to support dropping of modelnames in forms'''
print('dropEvent(%s): %s,%s'%(event,event.mimeData(),event.mimeData().formats()))
if event.source() is self:
print('Internal drag/drop not allowed')
return
if any(s in event.mimeData().formats() for s in self.mimeTypes()):
mtype = self.handleMimeData(event.mimeData(),self.addModels)#lambda m:self.addModels('^%s$'%m))
event.acceptProposedAction()
else:
print('Invalid model in dropped data')
[docs] def handleMimeData(self, mimeData, method):
'''Selects the most appropriate data from the given mimeData object
(in the order returned by :meth:`getSupportedMimeTypes`) and passes
it to the given method.
:param mimeData: (QMimeData) the MIME data object from which the model
is to be extracted
:param method: (callable<str>) a method that accepts a string as argument.
This method will be called with the data from the mimeData object
:return: (str or None) returns the MimeType used if the model was
successfully set, or None if the model could not be set
'''
print('QDropTextEdit.handleMimeData(%s,%s)'%(mimeData,method))
supported = self.mimeTypes()
print map(str,supported)
formats = mimeData.formats()
print map(str,formats)
for mtype in supported:
if mtype in formats:
d = str(mimeData.data(mtype))
if d is None:
return None
try:
method(d)
return mtype
except:
print('Invalid data (%s) for MIMETYPE=%s'%(repr(d), repr(mtype)))
#self.traceback(taurus.Debug)
return None
[docs]class QGridTable(Qt.QFrame):#Qt.QFrame):
"""
This class is a frame with a QGridLayout that emulates some methods of QTableWidget
"""
def __init__(self,parent=None):
Qt.QFrame.__init__(self,parent)
self.setLayout(Qt.QGridLayout())
self._widgets = []
[docs] def rowCount(self):
return self.layout().rowCount()
[docs] def setRowCount(self,count):
return None
[docs] def columnCount(self):
return self.layout().columnCount()
[docs] def setColumnCount(self,count):
None
[docs] def itemAt(self,x,y):
return self.layout().itemAtPosition(x,y)
[docs] def setItem(self,x,y,item,spanx=1,spany=1):
#print 'QGridTable.setItem(%s,%s,%s)'%(x,y,item)
self.layout().addWidget(item,x,y,spanx,spany)
if item not in self._widgets: self._widgets.append(item)
[docs] def resizeColumnsToContents(self):
return None
[docs] def resizeRowsToContents(self):
return None
[docs] def setRowHeight(self,row,height):
self.layout().setRowMinimumHeight(row,height)
[docs] def setColumnWidth(self,col,width):
self.layout().setColumnMinimumWidth(col,width)
[docs] def clear(self):
def deleteItems(layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
deleteItems(item.layout())
deleteItems(self.layout())
#l = self.layout()
#l.deleteLater()
#self.setLayout(Qt.QGridLayout())
self._widgets = []
[docs]class QDictTextBrowser(Qt.QWidget):
"""
Easy widget, it just shows a dictionary in a text shape
"""
def __init__(self,parent=None):
Qt.QWidget.__init__(self,parent)
self.model = {}
self.setupUi()
[docs] def setupUi(self):
self.top = Qt.QListWidget()
try:
self.top.itemClicked.connect(self.updateText)
except:
self.top.connect(self.top,Qt.SIGNAL('itemClicked(item)'),self.updateText)
self.bottom = Qt.QTextBrowser()
self.setLayout(Qt.QVBoxLayout())
map(self.layout().addWidget,(self.top,self.bottom))
[docs] def setModel(self,model):
self.model = model
self.top.clear()
for k in sorted(self.model.keys()):
self.top.addItem(k)
self.top.setMinimumHeight(125)
self.top.setMaximumHeight(max((125,len(self.model)*25)))
[docs] def updateText(self,item):
txt = self.model.get(str(item.text()))
txt = str(txt).replace('\t',' ')
self.emit(Qt.SIGNAL('textChanged'),txt)
self.bottom.setHtml('<pre>%s</pre>'%txt)
@staticmethod
[docs] def test():
app = getApplication()
w = QDictTextBrowser()
w.setModel({'1':'na\na'*100,'2':'no\nsi'})
w.show()
app.exec_()
[docs]def GetFramedTaurusValue(model=None,label=True,hook=None):
from taurus.qt.qtgui.panel import TaurusValue
frame = QtGui.QFrame()
frame.setLayout(QtGui.QGridLayout())
frame.layout().setContentsMargins(2,2,2,2)
frame.layout().setSpacing(0)
frame.layout().setSpacing(0)
frame.taurusvalue = TaurusValue(frame)
if hook:
frame.taurusvalue.connect(frame.taurusvalue, QtCore.SIGNAL("itemClicked(QString)"), hook)
if label:
frame.taurusvalue.setLabelConfig('label') ## DO NOT DELETE THIS LINE!!!
else:
frame.taurusvalue.setLabelWidgetClass(None)
if model:
frame.taurusvalue.setModel(model)
return frame
[docs]class QEvaluator(Qt.QWidget):
"""
Default methods:
mdir(expr,[module]): return elements matching expr, from module or locals()
help(method): returns source of method or __doc__
doc(method): idem
run(module): loads a module (reloads?)
table/bold/color: html formatting
"""
def __init__(self,parent=None,model='',filename='~/.qeval_history'): #'import fandango'):
import fandango.web, fandango.functional
print('%s()'%type(self).__name__)
Qt.QWidget.__init__(self,parent)
try:
self.name = type(self).__name__
self._locals = {'self':self,'load':self.setModel,'printf':self.printf,'Qt':Qt}
self._locals.update([(k,v) for k,v in fandango.functional.__dict__.items() if isCallable(v)])
self._locals['mdir'] = self.dir_module
self._locals['help'] = self.help
self._locals['doc'] = lambda x:x.__doc__
self._locals['run'] = fandango.objects.loadModule
self._locals.update((k,getattr(fandango.web,k)) for k in ('table','bold','color'))
self._modules = {}
self._instances = {}
self.history = []
self.filename = filename.replace('~',os.getenv('HOME')) if filename.startswith('~') else filename
try:#Enabling Syntax Highlighting
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
lexer,frmt=PythonLexer(),HtmlFormatter()
self.css=frmt.get_style_defs('.highlight')
self.highlight = lambda t,l=lexer,f=frmt: highlight(t,l,f)
#html='<head><style>%s</style><body>%s</body>'%(css,highlight(code,lexer,frmt))
except:
traceback.print_exc()
self.css = None
self.highlight = lambda t: '<pre>%s</pre>'%t
self.evalQ('import fandango')
self.evalQ('import fandango.qt')
self.evalQ('f=fandango')
self.setup_ui()
self.setEval()
self.setModel(model or '')
except:
traceback.print_exc()
[docs] def dir_module(self,*args):
f = fandango.first((a for a in args if isString(a)),'*')
if '*' not in f and not f.startswith('_'): f = '*%s*'%f
ks = fandango.first((dir(a) for a in args if not isString(a)),self._locals.keys())
return sorted((t for t in fandango.matchAll(f,ks) if f.startswith('_') or not t.startswith('__')),key=str.lower)
[docs] def help(self,m=None):
try:
if m:
import inspect
return '%s\n%s'%(inspect.getsource(m).split(':')[0].replace('\n',''),m.__doc__)
else:
return self.__doc__
except: return str(m)
[docs] def setEval(self,m=None):
self._eval = m or self.evalQ
[docs] def setModel(self,model=None):
"""
setModel(obj) will set the last element of a sequence of commands as Model for the shell
The Model can be either an object, class, module, ...
"""
print 'QEvaluator.setModel(%s)'%model
try:
if model:
if isString(model):
for c in model.split(';'):
try: model,o = c,self.evalQ(c)
except:
if len(c.split())==1:
model,o = 'import '+c,self.evalQ('import '+c)
else:
traceback.print_exc()
if '=' in model:
self.model,dct = model.split()[0],self._locals
elif 'import' in model:
self.model,dct = model.split()[-1],self._modules
else:
self.model,dct = model,{model:o}
self.target = dct[self.model]
else:
self.model = str(model)
self.target = model
self._locals['model'] = self.target
self.commands = fandango.matchAll('^[a-zA-Z]*',dir(self.target))
self.combo.clear()
self.combo.addItems(sorted(self.commands,key=str.lower))
else:
self.model = None
self.target = None
self.commands = []
self.combo.clear()
self.combo.addItems(list(reversed(self.history)))
self.setWindowTitle('FANDANGO.%s(%s)'%(self.name,self.model or ''))
except:
w = QExceptionMessage()
[docs] def evalQ(self,c):
return fandango.evalX(c,_locals=self._locals,modules=self._modules,instances=self._instances,_trace=True)
[docs] def setup_ui(self):
try:
from PyQt4.QtWebKit import QWebView
except:
QWebView = Qt.QTextBrowser
self.combo = Qt.QComboBox(self)
self.combo.setEditable(True) #self.model in (None,'fandango'))
self.args = Qt.QComboBox(self) #Qt.QLineEdit(self)
self.args.setEditable(True)
self.result = QWebView(self)
self.result.setHtml('<html><head><style>%s</style></head><body></body></html>'%(self.css))
self.button = Qt.QPushButton('Execute!')
self.export = Qt.QPushButton('Save History')
self.mlbt = Qt.QPushButton('ml')
self.mlbt.setMaximumWidth(50)
self.mlload = Qt.QPushButton('load history')
self.mlex = Qt.QPushButton('Execute!')
self.mledit = Qt.QTextEdit()
self.shortcut = Qt.QShortcut(self)
self.shortcut.setKey(Qt.QKeySequence("Ctrl+Enter"))
self.connect(self.shortcut, Qt.SIGNAL("activated()"), self.button.animateClick) #self.execute)
self.check = Qt.QCheckBox('Append')
self.check.setChecked(True)
self.setLayout(Qt.QGridLayout())
self.layout().addWidget(Qt.QLabel('Write python code or load(module) and select from the list'),0,0,1,4)
self.layout().addWidget(Qt.QLabel('function/statement'),1,0,1,1)
self.layout().addWidget(self.combo,1,1,1,3)
self.layout().addWidget(self.mlbt,1,4,1,1)
self.layout().addWidget(Qt.QLabel('arguments'),2,0,1,1)
self.layout().addWidget(self.args,2,1,1,3)
self.layout().addWidget(self.result,3,0,4,5)
self.layout().addWidget(self.button,8,0,1,2)
self.layout().addWidget(self.export,8,2,1,1)
self.layout().addWidget(self.check,8,3,1,1)
self.connect(self.button,Qt.SIGNAL('clicked()'),self.execute)
self.connect(self.export,Qt.SIGNAL('clicked()'),self.save_to)
self.connect(self.mlbt,Qt.SIGNAL('clicked()'),self.multiline_edit)
self.connect(self.mlex,Qt.SIGNAL('clicked()'),self.multiline_exec)
self.connect(self.mlload,Qt.SIGNAL('clicked()'),self.multiline_load)
[docs] def multiline_edit(self):
if not hasattr(self,'mlqd'):
self.mlqd = Qt.QDialog(self)
self.mlqd.setWindowTitle('Editor'),self.mlqd.setLayout(Qt.QVBoxLayout())
map(self.mlqd.layout().addWidget,(self.mlload,self.mledit,self.mlex))
self.mlqd.show()
[docs] def multiline_exec(self):
[self.execute(l.strip(),'') for l in str(self.mledit.toPlainText()).split('\n')]
[docs] def multiline_load(self):
self.mledit.setText('\n'.join(self.history))
[docs] def execute(self,cmd=None,args=None):
"""
(Ctrl+Enter) Send the current command,arguments to the eval engine and process the output.
"""
if not cmd and not args:
cmd,args = str(self.combo.currentText()),str(self.args.currentText()) #text())
print('execute: %s(%s)'%(cmd,args))
if self.filename:
try: open(self.filename,'a').write('%s.%s(%s)\n'%(self.model,cmd,args))
except: pass
try:
q = cmd if cmd not in self.commands else 'self.target.%s'%(cmd)
o = self._eval(q)
if fandango.isCallable(o) and args:
print '%s(%s)'%(o,args)
self._locals['_ftmp'] = o
o = self._eval('_ftmp(%s)'%(args))
self.history.append('%s(%s)'%(o,args))
else: self.history.append(q)
#print(self.history[-1])
except:
o = traceback.format_exc()
print(o)
txt = '\n'.join(map(str,o.items()) if hasattr(o,'items') else ([(str(t).strip('\n\r ') or getattr(t,'__name__','?')) for t in o] if isSequence(o) else [str(o)]))
isHtml = txt.strip().startswith('<') and txt.strip().endswith('>') and '</' in txt
txt = ('execute: %s(%s) => '%(cmd,args))+type(o).__name__+':\n'+'-'*80+'\n' + txt
self.printf(txt,append=self.check.isChecked(),highlight=not isHtml)
if self.model is None and not any(i>1 and cmd==str(self.combo.itemText(i)) for i in range(self.combo.count())):
self.combo.insertItems(0,[cmd])
self.args.insertItems(0,[args])
[docs] def save_to(self,filename=None,txt=None):
pass
[docs] def printf(self,txt,append=True,highlight=False,curr=''):
if append:
try:
if hasattr(self.result,'page'):
curr = self.result.page().currentFrame().toHtml().replace('</body></html>','')
plain = self.result.page().currentFrame().toPlainText()
else:
curr = self.result.toHtml().replace('</body></html>','')
plain = self.result.toPlainText()
except:
traceback.print_exc()
curr = ''
txt = self.highlight(txt) if (highlight is True) else txt #highlight=1 can still be used to force highlighting
if (self.css and '<style>' not in curr) or not curr or not append:
txt = ('<html><head><style>%s</style></head><body>%s</body></html>'%(self.css,txt))
else:
txt = (str(curr)+'<br>'+txt+'</body></html>')
self.result.setHtml(txt)
try:
self.result.keyPressEvent(Qt.QKeyEvent(Qt.QEvent.KeyPress,Qt.Qt.Key_End,Qt.Qt.NoModifier))
#self.result.page().mainFrame().scroll(0,self.result.page().mainFrame().contentsSize().height())
except:
traceback.print_exc()
@staticmethod
[docs] def main(args=None):
qapp = getApplication()
print(len(args),args)
args = notNone(args,sys.argv[1:])
if args and os.path.isfile(args[0]):
args = filter(bool,map(str.strip,open(args[0]).readlines()))
kw = args and {'model':';'.join(args)} or {}
print('model',kw.get('model'))
w = QEvaluator(**kw)
w.show()
qapp.exec_()
[docs]class ApiBrowser(QEvaluator): pass #For backwards compatibility
if __name__ == '__main__':
QEvaluator.main(sys.argv[1:])
from . import doc
__doc__ = doc.get_fn_autodoc(__name__,vars())