class parameter_dialog_or_frame: """ use as a pre-mixin before QDialog or QFrame """ ####@@@@ def __init__(self, parent=None, desc=None, name=None, modal=0, fl=0, env=None, type="QDialog"): if env is None: import foundation.env as env # this is a little weird... probably it'll be ok, and logically it seems correct. self.desc = desc self.typ = type if type == "QDialog": QDialog.__init__(self, parent, name, modal, fl) elif type == "QTextEdit": QTextEdit.__init__(self, parent, name) elif type == "QFrame": QFrame.__init__(self, parent, name) else: print "don't know about type == %r" % (type, ) self.image1 = QPixmap() self.image1.loadFromData(image1_data, "PNG") # should be: title_icon #### self.image3 = QPixmap() self.image3.loadFromData(image3_data, "PNG") self.image4 = QPixmap() self.image4.loadFromData(image4_data, "PNG") self.image5 = QPixmap() self.image5.loadFromData(image5_data, "PNG") self.image6 = QPixmap() self.image6.loadFromData(image6_data, "PNG") self.image7 = QPixmap() self.image7.loadFromData(image7_data, "PNG") self.image0 = QPixmap(image0_data) # should be: border_icon #### self.image2 = QPixmap(image2_data) # should be: sponsor_pixmap #### try: ####@@@@ title_icon_name = self.desc.options.get('title_icon') border_icon_name = self.desc.options.get('border_icon') if title_icon_name: self.image1 = imagename_to_pixmap( title_icon_name) ###@@@ pass icon_path ###@@@ import imagename_to_pixmap or use env function # or let that func itself be an arg, or have an env arg for it ###e rename it icon_name_to_pixmap, or find_icon? (the latter only if it's ok if it returns an iconset) ###e use iconset instead? if border_icon_name: self.image0 = imagename_to_pixmap(border_icon_name) except: print_compact_traceback( "bug in icon-setting code, using fallback icons: ") pass if not name: self.setName("parameter_dialog_or_frame") ### ###k guess this will need: if type == 'QDialog' self.setIcon(self.image0) # should be: border_icon #### nanotube_dialogLayout = QVBoxLayout(self, 0, 0, "nanotube_dialogLayout") self.heading_frame = QFrame(self, "heading_frame") self.heading_frame.setPaletteBackgroundColor(QColor(122, 122, 122)) self.heading_frame.setFrameShape(QFrame.NoFrame) self.heading_frame.setFrameShadow(QFrame.Plain) heading_frameLayout = QHBoxLayout(self.heading_frame, 0, 3, "heading_frameLayout") self.heading_pixmap = QLabel(self.heading_frame, "heading_pixmap") self.heading_pixmap.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed, 0, 0, self.heading_pixmap.sizePolicy().hasHeightForWidth())) self.heading_pixmap.setPixmap( self.image1) # should be: title_icon #### self.heading_pixmap.setScaledContents(1) heading_frameLayout.addWidget(self.heading_pixmap) self.heading_label = QLabel(self.heading_frame, "heading_label") self.heading_label.setPaletteForegroundColor(QColor(255, 255, 255)) heading_label_font = QFont(self.heading_label.font()) heading_label_font.setPointSize(12) heading_label_font.setBold(1) self.heading_label.setFont(heading_label_font) heading_frameLayout.addWidget(self.heading_label) nanotube_dialogLayout.addWidget(self.heading_frame) self.body_frame = QFrame(self, "body_frame") self.body_frame.setFrameShape(QFrame.StyledPanel) self.body_frame.setFrameShadow(QFrame.Raised) body_frameLayout = QVBoxLayout(self.body_frame, 3, 3, "body_frameLayout") self.sponsor_frame = QFrame(self.body_frame, "sponsor_frame") self.sponsor_frame.setPaletteBackgroundColor(QColor(255, 255, 255)) self.sponsor_frame.setFrameShape(QFrame.StyledPanel) self.sponsor_frame.setFrameShadow(QFrame.Raised) sponsor_frameLayout = QHBoxLayout(self.sponsor_frame, 0, 0, "sponsor_frameLayout") self.sponsor_btn = QPushButton(self.sponsor_frame, "sponsor_btn") self.sponsor_btn.setAutoDefault(0) #bruce 060703 bugfix self.sponsor_btn.setSizePolicy( QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, 0, 0, self.sponsor_btn.sizePolicy().hasHeightForWidth())) self.sponsor_btn.setPaletteBackgroundColor(QColor(255, 255, 255)) self.sponsor_btn.setPixmap( self.image2 ) # should be: sponsor_pixmap #### [also we'll need to support >1 sponsor] self.sponsor_btn.setFlat(1) sponsor_frameLayout.addWidget(self.sponsor_btn) body_frameLayout.addWidget(self.sponsor_frame) layout59 = QHBoxLayout(None, 0, 6, "layout59") left_spacer = QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) layout59.addItem(left_spacer) self.done_btn = QToolButton(self.body_frame, "done_btn") self.done_btn.setIcon(QIcon(self.image3)) layout59.addWidget(self.done_btn) self.abort_btn = QToolButton(self.body_frame, "abort_btn") self.abort_btn.setIcon(QIcon(self.image4)) layout59.addWidget(self.abort_btn) self.preview_btn = QToolButton(self.body_frame, "preview_btn") self.preview_btn.setIcon(QIcon(self.image5)) layout59.addWidget(self.preview_btn) self.whatsthis_btn = QToolButton(self.body_frame, "whatsthis_btn") self.whatsthis_btn.setIcon(QIcon(self.image6)) layout59.addWidget(self.whatsthis_btn) right_spacer = QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) layout59.addItem(right_spacer) body_frameLayout.addLayout(layout59) self.groups = [] self.param_getters = { } # map from param name to get-function (which gets current value out of its widget or controller) for group_desc in self.desc.kids('group'): # == start parameters_grpbox ### this will differ for Windows style header_refs = [ ] # keep python refcounted refs to all objects we make (at least the ones pyuic stored in self attrs) self.parameters_grpbox = QGroupBox(self.body_frame, "parameters_grpbox") self.parameters_grpbox.setFrameShape(QGroupBox.StyledPanel) self.parameters_grpbox.setFrameShadow(QGroupBox.Sunken) self.parameters_grpbox.setMargin(0) self.parameters_grpbox.setColumnLayout(0, Qt.Vertical) self.parameters_grpbox.layout().setSpacing(1) self.parameters_grpbox.layout().setMargin(4) parameters_grpboxLayout = QVBoxLayout( self.parameters_grpbox.layout()) parameters_grpboxLayout.setAlignment(Qt.AlignTop) layout20 = QHBoxLayout(None, 0, 6, "layout20") self.nt_parameters_grpbtn = QPushButton(self.parameters_grpbox, "nt_parameters_grpbtn") self.nt_parameters_grpbtn.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed, 0, 0, self.nt_parameters_grpbtn.sizePolicy().hasHeightForWidth()) ) self.nt_parameters_grpbtn.setMaximumSize(QSize(16, 16)) self.nt_parameters_grpbtn.setAutoDefault(0) self.nt_parameters_grpbtn.setIcon(QIcon( self.image7)) ### not always right, but doesn't matter self.nt_parameters_grpbtn.setFlat(1) layout20.addWidget(self.nt_parameters_grpbtn) self.parameters_grpbox_label = QLabel(self.parameters_grpbox, "parameters_grpbox_label") self.parameters_grpbox_label.setSizePolicy( QSizePolicy( QSizePolicy.Preferred, QSizePolicy.Minimum, 0, 0, self.parameters_grpbox_label.sizePolicy(). hasHeightForWidth())) self.parameters_grpbox_label.setAlignment(QLabel.AlignVCenter) layout20.addWidget(self.parameters_grpbox_label) gbx_spacer1 = QSpacerItem(67, 16, QSizePolicy.Expanding, QSizePolicy.Minimum) layout20.addItem(gbx_spacer1) parameters_grpboxLayout.addLayout(layout20) nt_parameters_body_layout = QGridLayout( None, 1, 1, 0, 6, "nt_parameters_body_layout" ) ### what is 6 -- is it related to number of items??? # is it 6 in all the ones we got, but that could be a designer error so i better look it up sometime. # == start its kids # will use from above: self.parameters_grpbox, nt_parameters_body_layout nextrow = 0 # which row of the QGridLayout to start filling next (loop variable) hidethese = [ ] # set of objects to hide or show, when this group is closed or opened for param in group_desc.kids('parameter'): # param (a group subobj desc) is always a parameter, but we already plan to extend this beyond that, # so we redundantly test for this here. getter = None paramname = None # set these for use by uniform code at the end (e.g. for tooltips) editfield = None label = None if param.isa('parameter'): label = QLabel(self.parameters_grpbox, "members_label") label.setAlignment(QLabel.AlignVCenter | QLabel.AlignRight) nt_parameters_body_layout.addWidget(label, nextrow, 0) hidethese.append(label) thisrow = nextrow nextrow += 1 #e following should be known in a place that knows the input language, not here paramname = param.options.get('name') or ( param.args and param.args[0]) or "?" paramlabel = param.options.get( 'label' ) or paramname ##e wrong, label "" or none ought to be possible # QtGui.QApplication.translate(self.__class__.__name__, "xyz") label.setText( QtGui.QApplication.translate(self.__class__.__name__, paramlabel)) if param.isa('parameter', widget='combobox', type=('str', None)): self.members_combox = QComboBox( 0, self.parameters_grpbox, "members_combox") ###k what's 0? editfield = self.members_combox #### it probably needs a handler class, and then that could do this setup self.members_combox.clear() default = param.options.get( 'default', None) # None is not equal to any string thewidgetkid = param.kids( 'widget' )[-1] # kluge; need to think what the desc method for this should be for item in thewidgetkid.kids('item'): itemval = item.args[0] itemtext = itemval self.members_combox.insertItem( QtGui.QApplication.translate( self.__class__.__name__, itemtext)) #k __tr ok?? if itemval == default: #k or itemtext? pass ##k i find no setItem in our py code, so not sure yet what to do for this. nt_parameters_body_layout.addWidget( self.members_combox, thisrow, 1) hidethese.append(self.members_combox) getter = (lambda combobox=self.members_combox: str( combobox.currentText())) ##e due to __tr or non-str values, it might be better to use currentIndex and look it up in a table # (though whether __tr is good here might depend on what it's used for) elif param.isa('parameter', widget=('lineedit', None), type=('str', None)): # this covers explicit str|lineedit, and 3 default cases str, lineedit, neither. # (i.e. if you say parameter and nothing else, it's str lineedit by default.) self.length_linedit = QLineEdit(self.parameters_grpbox, "length_linedit") editfield = self.length_linedit nt_parameters_body_layout.addWidget( self.length_linedit, thisrow, 1) hidethese.append(self.length_linedit) default = str(param.options.get('default', "")) self.length_linedit.setText( QtGui.QApplication.translate(self.__class__.__name__, default)) # __tr ok? getter = (lambda lineedit=self.length_linedit: str( lineedit.text())) elif param.isa('parameter', widget=('lineedit', None), type='float'): self.length_linedit = QLineEdit(self.parameters_grpbox, "length_linedit") editfield = self.length_linedit nt_parameters_body_layout.addWidget( self.length_linedit, thisrow, 1) hidethese.append(self.length_linedit) controller = FloatLineeditController_Qt( self, param, self.length_linedit) header_refs.append(controller) getter = controller.get_value elif param.isa('parameter', widget = ('spinbox', None), type = 'int') or \ param.isa('parameter', widget = ('spinbox'), type = None): self.chirality_N_spinbox = QSpinBox( self.parameters_grpbox, "chirality_N_spinbox" ) # was chirality_m_spinbox, now chirality_N_spinbox editfield = self.chirality_N_spinbox ### seems like Qt defaults for min and max are 0,100 -- way too small a range! if param.options.has_key('min') or 1: self.chirality_N_spinbox.setMinimum( param.options.get('min', -999999999)) # was 0 if param.options.has_key('max') or 1: self.chirality_N_spinbox.setMaximum( param.options.get( 'max', +999999999)) # wasn't in egcode, but needed self.chirality_N_spinbox.setValue( param.options.get('default', 0)) # was 5 ##e note: i suspect this default 0 should come from something that knows this desc grammar. suffix = param.options.get('suffix', '') if suffix: self.chirality_N_spinbox.setSuffix( QtGui.QApplication.translate( self.__class__.__name__, suffix)) else: self.chirality_N_spinbox.setSuffix( QString.null) # probably not needed nt_parameters_body_layout.addWidget( self.chirality_N_spinbox, thisrow, 1) hidethese.append(self.chirality_N_spinbox) getter = self.chirality_N_spinbox.value # note: it also has .text, which includes suffix else: print "didn't match:", param ###e improve this # things done the same way for all kinds of param-editing widgets if 1: #bruce 060703 moved this down here, as bugfix # set tooltip (same one for editfield and label) tooltip = param.options.get('tooltip', '') ###e do it for more kinds of params; share the code somehow; do it in controller, or setup-aid? ###k QToolTip appropriateness; tooltip option might be entirely untested if tooltip and label: QToolTip.add( label, QtGui.QApplication.translate( self.__class__.__name__, tooltip)) if tooltip and editfield: QToolTip.add( editfield, QtGui.QApplication.translate( self.__class__.__name__, tooltip) ) ##k ok?? review once not all params have same-row labels. if getter and paramname and paramname != '?': self.param_getters[paramname] = getter ### also bind these params to actions... continue # next param header_refs.extend([ self.parameters_grpbox, self.nt_parameters_grpbtn, self.parameters_grpbox_label ]) # now create the logic/control object for the group group = CollapsibleGroupController_Qt(self, group_desc, header_refs, hidethese, self.nt_parameters_grpbtn) ### maybe ask env for the class to use for this? self.groups.append( group ) ### needed?? only for scanning the params, AFAIK -- oh, and to maintain a python refcount. # from languageChange: if 1: # i don't know if these are needed: self.parameters_grpbox.setTitle(QString.null) self.nt_parameters_grpbtn.setText(QString.null) self.parameters_grpbox_label.setText( QtGui.QApplication.translate( self.__class__.__name__, group_desc.args[0])) # was "Nanotube Parameters" ##e note that it's questionable in the syntax design for this property of a group (overall group label) # to be in that position (desc arg 0). # == end its kids parameters_grpboxLayout.addLayout(nt_parameters_body_layout) body_frameLayout.addWidget(self.parameters_grpbox) # == end parameters groupbox continue # next group nanotube_dialogLayout.addWidget(self.body_frame) spacer14 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) nanotube_dialogLayout.addItem(spacer14) layout42 = QHBoxLayout(None, 4, 6, "layout42") btm_spacer = QSpacerItem(59, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) layout42.addItem(btm_spacer) self.cancel_btn = QPushButton(self, "cancel_btn") self.cancel_btn.setAutoDefault(0) #bruce 060703 bugfix layout42.addWidget(self.cancel_btn) self.ok_btn = QPushButton(self, "ok_btn") self.ok_btn.setAutoDefault(0) #bruce 060703 bugfix layout42.addWidget(self.ok_btn) nanotube_dialogLayout.addLayout(layout42) self.languageChange() self.resize( QSize(246, 618).expandedTo(self.minimumSizeHint()) ) ### this size will need to be adjusted (guess -- it's only place overall size is set) qt4todo('self.clearWState(Qt.WState_Polished)') ## self.connect(self.nt_parameters_grpbtn,SIGNAL("clicked()"),self.toggle_nt_parameters_grpbtn) #### # new: for button, methodname in ( (self.sponsor_btn, 'do_sponsor_btn'), #e generalize to more than one sponsor button (self.done_btn, 'do_done_btn'), (self.abort_btn, 'do_abort_btn'), (self.preview_btn, 'do_preview_btn'), (self.whatsthis_btn, 'do_whatsthis_btn'), (self.cancel_btn, 'do_cancel_btn'), (self.ok_btn, 'do_ok_btn')): if hasattr(self, methodname): self.connect(button, SIGNAL("clicked()"), getattr(self, methodname)) return def languageChange(self): opts = self.desc.option_attrs self.setCaption( QtGui.QApplication.translate(self.__class__.__name__, opts.caption)) # was "Nanotube" self.heading_label.setText( QtGui.QApplication.translate(self.__class__.__name__, opts.title)) # was "Nanotube" self.sponsor_btn.setText(QString.null) self.done_btn.setText(QString.null) QToolTip.add( self.done_btn, QtGui.QApplication.translate(self.__class__.__name__, "Done")) self.abort_btn.setText(QString.null) QToolTip.add( self.abort_btn, QtGui.QApplication.translate(self.__class__.__name__, "Cancel")) self.preview_btn.setText(QString.null) QToolTip.add( self.preview_btn, QtGui.QApplication.translate(self.__class__.__name__, "Preview")) self.whatsthis_btn.setText(QString.null) QToolTip.add( self.whatsthis_btn, QtGui.QApplication.translate(self.__class__.__name__, "What's This Help")) ### move these up: ## if 0: ## self.parameters_grpbox.setTitle(QString.null) ## self.nt_parameters_grpbtn.setText(QString.null) ## self.parameters_grpbox_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Nanotube Parameters")) ## if 0: ## self.members_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Members :")) ## self.length_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Length :")) ## self.chirality_n_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Chirality (n) :")) ## self.members_combox.clear() ## self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, "C - C")) ## self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, "B - N")) ## self.length_linedit.setText(QtGui.QApplication.translate(self.__class__.__name__, "20.0 A")) ## self.chirality_N_spinbox.setSuffix(QString.null) self.cancel_btn.setText( QtGui.QApplication.translate(self.__class__.__name__, "Cancel")) self.ok_btn.setText( QtGui.QApplication.translate(self.__class__.__name__, "OK")) return pass # end of class parameter_dialog_or_frame -- maybe it should be renamed
def clear_history(self, *args): QComboBox.clear(self)
def clear(self): QComboBox.clear(self) self.initialize_saved_search_names() self.setEditText('') self.line_edit.home(False)
class parameter_dialog_or_frame: """ use as a pre-mixin before QDialog or QFrame """ ####@@@@ def __init__(self, parent = None, desc = None, name = None, modal = 0, fl = 0, env = None, type = "QDialog"): if env is None: import foundation.env as env # this is a little weird... probably it'll be ok, and logically it seems correct. self.desc = desc self.typ = type if type == "QDialog": QDialog.__init__(self,parent,name,modal,fl) elif type == "QTextEdit": QTextEdit.__init__(self, parent, name) elif type == "QFrame": QFrame.__init__(self,parent,name) else: print "don't know about type == %r" % (type,) self.image1 = QPixmap() self.image1.loadFromData(image1_data,"PNG") # should be: title_icon #### self.image3 = QPixmap() self.image3.loadFromData(image3_data,"PNG") self.image4 = QPixmap() self.image4.loadFromData(image4_data,"PNG") self.image5 = QPixmap() self.image5.loadFromData(image5_data,"PNG") self.image6 = QPixmap() self.image6.loadFromData(image6_data,"PNG") self.image7 = QPixmap() self.image7.loadFromData(image7_data,"PNG") self.image0 = QPixmap(image0_data) # should be: border_icon #### self.image2 = QPixmap(image2_data) # should be: sponsor_pixmap #### try: ####@@@@ title_icon_name = self.desc.options.get('title_icon') border_icon_name = self.desc.options.get('border_icon') if title_icon_name: self.image1 = imagename_to_pixmap(title_icon_name) ###@@@ pass icon_path ###@@@ import imagename_to_pixmap or use env function # or let that func itself be an arg, or have an env arg for it ###e rename it icon_name_to_pixmap, or find_icon? (the latter only if it's ok if it returns an iconset) ###e use iconset instead? if border_icon_name: self.image0 = imagename_to_pixmap(border_icon_name) except: print_compact_traceback("bug in icon-setting code, using fallback icons: ") pass if not name: self.setName("parameter_dialog_or_frame") ### ###k guess this will need: if type == 'QDialog' self.setIcon(self.image0) # should be: border_icon #### nanotube_dialogLayout = QVBoxLayout(self,0,0,"nanotube_dialogLayout") self.heading_frame = QFrame(self,"heading_frame") self.heading_frame.setPaletteBackgroundColor(QColor(122,122,122)) self.heading_frame.setFrameShape(QFrame.NoFrame) self.heading_frame.setFrameShadow(QFrame.Plain) heading_frameLayout = QHBoxLayout(self.heading_frame,0,3,"heading_frameLayout") self.heading_pixmap = QLabel(self.heading_frame,"heading_pixmap") self.heading_pixmap.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed,0,0,self.heading_pixmap.sizePolicy().hasHeightForWidth())) self.heading_pixmap.setPixmap(self.image1) # should be: title_icon #### self.heading_pixmap.setScaledContents(1) heading_frameLayout.addWidget(self.heading_pixmap) self.heading_label = QLabel(self.heading_frame,"heading_label") self.heading_label.setPaletteForegroundColor(QColor(255,255,255)) heading_label_font = QFont(self.heading_label.font()) heading_label_font.setPointSize(12) heading_label_font.setBold(1) self.heading_label.setFont(heading_label_font) heading_frameLayout.addWidget(self.heading_label) nanotube_dialogLayout.addWidget(self.heading_frame) self.body_frame = QFrame(self,"body_frame") self.body_frame.setFrameShape(QFrame.StyledPanel) self.body_frame.setFrameShadow(QFrame.Raised) body_frameLayout = QVBoxLayout(self.body_frame,3,3,"body_frameLayout") self.sponsor_frame = QFrame(self.body_frame,"sponsor_frame") self.sponsor_frame.setPaletteBackgroundColor(QColor(255,255,255)) self.sponsor_frame.setFrameShape(QFrame.StyledPanel) self.sponsor_frame.setFrameShadow(QFrame.Raised) sponsor_frameLayout = QHBoxLayout(self.sponsor_frame,0,0,"sponsor_frameLayout") self.sponsor_btn = QPushButton(self.sponsor_frame,"sponsor_btn") self.sponsor_btn.setAutoDefault(0) #bruce 060703 bugfix self.sponsor_btn.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,QSizePolicy.Preferred,0,0,self.sponsor_btn.sizePolicy().hasHeightForWidth())) self.sponsor_btn.setPaletteBackgroundColor(QColor(255,255,255)) self.sponsor_btn.setPixmap(self.image2) # should be: sponsor_pixmap #### [also we'll need to support >1 sponsor] self.sponsor_btn.setFlat(1) sponsor_frameLayout.addWidget(self.sponsor_btn) body_frameLayout.addWidget(self.sponsor_frame) layout59 = QHBoxLayout(None,0,6,"layout59") left_spacer = QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum) layout59.addItem(left_spacer) self.done_btn = QToolButton(self.body_frame,"done_btn") self.done_btn.setIcon(QIcon(self.image3)) layout59.addWidget(self.done_btn) self.abort_btn = QToolButton(self.body_frame,"abort_btn") self.abort_btn.setIcon(QIcon(self.image4)) layout59.addWidget(self.abort_btn) self.preview_btn = QToolButton(self.body_frame,"preview_btn") self.preview_btn.setIcon(QIcon(self.image5)) layout59.addWidget(self.preview_btn) self.whatsthis_btn = QToolButton(self.body_frame,"whatsthis_btn") self.whatsthis_btn.setIcon(QIcon(self.image6)) layout59.addWidget(self.whatsthis_btn) right_spacer = QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum) layout59.addItem(right_spacer) body_frameLayout.addLayout(layout59) self.groups = [] self.param_getters = {} # map from param name to get-function (which gets current value out of its widget or controller) for group_desc in self.desc.kids('group'): # == start parameters_grpbox ### this will differ for Windows style header_refs = [] # keep python refcounted refs to all objects we make (at least the ones pyuic stored in self attrs) self.parameters_grpbox = QGroupBox(self.body_frame,"parameters_grpbox") self.parameters_grpbox.setFrameShape(QGroupBox.StyledPanel) self.parameters_grpbox.setFrameShadow(QGroupBox.Sunken) self.parameters_grpbox.setMargin(0) self.parameters_grpbox.setColumnLayout(0,Qt.Vertical) self.parameters_grpbox.layout().setSpacing(1) self.parameters_grpbox.layout().setMargin(4) parameters_grpboxLayout = QVBoxLayout(self.parameters_grpbox.layout()) parameters_grpboxLayout.setAlignment(Qt.AlignTop) layout20 = QHBoxLayout(None,0,6,"layout20") self.nt_parameters_grpbtn = QPushButton(self.parameters_grpbox,"nt_parameters_grpbtn") self.nt_parameters_grpbtn.setSizePolicy(QSizePolicy(QSizePolicy.Minimum,QSizePolicy.Fixed,0,0,self.nt_parameters_grpbtn.sizePolicy().hasHeightForWidth())) self.nt_parameters_grpbtn.setMaximumSize(QSize(16,16)) self.nt_parameters_grpbtn.setAutoDefault(0) self.nt_parameters_grpbtn.setIcon(QIcon(self.image7)) ### not always right, but doesn't matter self.nt_parameters_grpbtn.setFlat(1) layout20.addWidget(self.nt_parameters_grpbtn) self.parameters_grpbox_label = QLabel(self.parameters_grpbox,"parameters_grpbox_label") self.parameters_grpbox_label.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,QSizePolicy.Minimum,0,0,self.parameters_grpbox_label.sizePolicy().hasHeightForWidth())) self.parameters_grpbox_label.setAlignment(QLabel.AlignVCenter) layout20.addWidget(self.parameters_grpbox_label) gbx_spacer1 = QSpacerItem(67,16,QSizePolicy.Expanding,QSizePolicy.Minimum) layout20.addItem(gbx_spacer1) parameters_grpboxLayout.addLayout(layout20) nt_parameters_body_layout = QGridLayout(None,1,1,0,6,"nt_parameters_body_layout") ### what is 6 -- is it related to number of items??? # is it 6 in all the ones we got, but that could be a designer error so i better look it up sometime. # == start its kids # will use from above: self.parameters_grpbox, nt_parameters_body_layout nextrow = 0 # which row of the QGridLayout to start filling next (loop variable) hidethese = [] # set of objects to hide or show, when this group is closed or opened for param in group_desc.kids('parameter'): # param (a group subobj desc) is always a parameter, but we already plan to extend this beyond that, # so we redundantly test for this here. getter = None paramname = None # set these for use by uniform code at the end (e.g. for tooltips) editfield = None label = None if param.isa('parameter'): label = QLabel(self.parameters_grpbox,"members_label") label.setAlignment(QLabel.AlignVCenter | QLabel.AlignRight) nt_parameters_body_layout.addWidget(label,nextrow,0) hidethese.append(label) thisrow = nextrow nextrow += 1 #e following should be known in a place that knows the input language, not here paramname = param.options.get('name') or (param.args and param.args[0]) or "?" paramlabel = param.options.get('label') or paramname ##e wrong, label "" or none ought to be possible # QtGui.QApplication.translate(self.__class__.__name__, "xyz") label.setText(QtGui.QApplication.translate(self.__class__.__name__, paramlabel)) if param.isa('parameter', widget = 'combobox', type = ('str',None)): self.members_combox = QComboBox(0,self.parameters_grpbox,"members_combox") ###k what's 0? editfield = self.members_combox #### it probably needs a handler class, and then that could do this setup self.members_combox.clear() default = param.options.get('default', None) # None is not equal to any string thewidgetkid = param.kids('widget')[-1] # kluge; need to think what the desc method for this should be for item in thewidgetkid.kids('item'): itemval = item.args[0] itemtext = itemval self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, itemtext)) #k __tr ok?? if itemval == default: #k or itemtext? pass ##k i find no setItem in our py code, so not sure yet what to do for this. nt_parameters_body_layout.addWidget(self.members_combox,thisrow,1) hidethese.append(self.members_combox) getter = (lambda combobox = self.members_combox: str(combobox.currentText())) ##e due to __tr or non-str values, it might be better to use currentIndex and look it up in a table # (though whether __tr is good here might depend on what it's used for) elif param.isa('parameter', widget = ('lineedit', None), type = ('str',None)): # this covers explicit str|lineedit, and 3 default cases str, lineedit, neither. # (i.e. if you say parameter and nothing else, it's str lineedit by default.) self.length_linedit = QLineEdit(self.parameters_grpbox,"length_linedit") editfield = self.length_linedit nt_parameters_body_layout.addWidget(self.length_linedit,thisrow,1) hidethese.append(self.length_linedit) default = str(param.options.get('default', "")) self.length_linedit.setText(QtGui.QApplication.translate(self.__class__.__name__, default)) # __tr ok? getter = (lambda lineedit = self.length_linedit: str(lineedit.text())) elif param.isa('parameter', widget = ('lineedit', None), type = 'float'): self.length_linedit = QLineEdit(self.parameters_grpbox,"length_linedit") editfield = self.length_linedit nt_parameters_body_layout.addWidget(self.length_linedit,thisrow,1) hidethese.append(self.length_linedit) controller = FloatLineeditController_Qt(self, param, self.length_linedit) header_refs.append(controller) getter = controller.get_value elif param.isa('parameter', widget = ('spinbox', None), type = 'int') or \ param.isa('parameter', widget = ('spinbox'), type = None): self.chirality_N_spinbox = QSpinBox(self.parameters_grpbox,"chirality_N_spinbox") # was chirality_m_spinbox, now chirality_N_spinbox editfield = self.chirality_N_spinbox ### seems like Qt defaults for min and max are 0,100 -- way too small a range! if param.options.has_key('min') or 1: self.chirality_N_spinbox.setMinimum(param.options.get('min', -999999999)) # was 0 if param.options.has_key('max') or 1: self.chirality_N_spinbox.setMaximum(param.options.get('max', +999999999)) # wasn't in egcode, but needed self.chirality_N_spinbox.setValue(param.options.get('default', 0)) # was 5 ##e note: i suspect this default 0 should come from something that knows this desc grammar. suffix = param.options.get('suffix', '') if suffix: self.chirality_N_spinbox.setSuffix(QtGui.QApplication.translate(self.__class__.__name__, suffix)) else: self.chirality_N_spinbox.setSuffix(QString.null) # probably not needed nt_parameters_body_layout.addWidget(self.chirality_N_spinbox,thisrow,1) hidethese.append(self.chirality_N_spinbox) getter = self.chirality_N_spinbox.value # note: it also has .text, which includes suffix else: print "didn't match:",param ###e improve this # things done the same way for all kinds of param-editing widgets if 1: #bruce 060703 moved this down here, as bugfix # set tooltip (same one for editfield and label) tooltip = param.options.get('tooltip', '') ###e do it for more kinds of params; share the code somehow; do it in controller, or setup-aid? ###k QToolTip appropriateness; tooltip option might be entirely untested if tooltip and label: QToolTip.add(label, QtGui.QApplication.translate(self.__class__.__name__, tooltip)) if tooltip and editfield: QToolTip.add(editfield, QtGui.QApplication.translate(self.__class__.__name__, tooltip)) ##k ok?? review once not all params have same-row labels. if getter and paramname and paramname != '?': self.param_getters[paramname] = getter ### also bind these params to actions... continue # next param header_refs.extend( [self.parameters_grpbox, self.nt_parameters_grpbtn, self.parameters_grpbox_label] ) # now create the logic/control object for the group group = CollapsibleGroupController_Qt(self, group_desc, header_refs, hidethese, self.nt_parameters_grpbtn) ### maybe ask env for the class to use for this? self.groups.append(group) ### needed?? only for scanning the params, AFAIK -- oh, and to maintain a python refcount. # from languageChange: if 1: # i don't know if these are needed: self.parameters_grpbox.setTitle(QString.null) self.nt_parameters_grpbtn.setText(QString.null) self.parameters_grpbox_label.setText(QtGui.QApplication.translate(self.__class__.__name__, group_desc.args[0])) # was "Nanotube Parameters" ##e note that it's questionable in the syntax design for this property of a group (overall group label) # to be in that position (desc arg 0). # == end its kids parameters_grpboxLayout.addLayout(nt_parameters_body_layout) body_frameLayout.addWidget(self.parameters_grpbox) # == end parameters groupbox continue # next group nanotube_dialogLayout.addWidget(self.body_frame) spacer14 = QSpacerItem(20,20,QSizePolicy.Minimum,QSizePolicy.Expanding) nanotube_dialogLayout.addItem(spacer14) layout42 = QHBoxLayout(None,4,6,"layout42") btm_spacer = QSpacerItem(59,20,QSizePolicy.Expanding,QSizePolicy.Minimum) layout42.addItem(btm_spacer) self.cancel_btn = QPushButton(self,"cancel_btn") self.cancel_btn.setAutoDefault(0) #bruce 060703 bugfix layout42.addWidget(self.cancel_btn) self.ok_btn = QPushButton(self,"ok_btn") self.ok_btn.setAutoDefault(0) #bruce 060703 bugfix layout42.addWidget(self.ok_btn) nanotube_dialogLayout.addLayout(layout42) self.languageChange() self.resize(QSize(246,618).expandedTo(self.minimumSizeHint())) ### this size will need to be adjusted (guess -- it's only place overall size is set) qt4todo('self.clearWState(Qt.WState_Polished)') ## self.connect(self.nt_parameters_grpbtn,SIGNAL("clicked()"),self.toggle_nt_parameters_grpbtn) #### # new: for button, methodname in ((self.sponsor_btn, 'do_sponsor_btn'), #e generalize to more than one sponsor button (self.done_btn, 'do_done_btn'), (self.abort_btn, 'do_abort_btn'), (self.preview_btn, 'do_preview_btn'), (self.whatsthis_btn, 'do_whatsthis_btn'), (self.cancel_btn, 'do_cancel_btn'), (self.ok_btn, 'do_ok_btn')): if hasattr(self, methodname): self.connect(button, SIGNAL("clicked()"), getattr(self, methodname)) return def languageChange(self): opts = self.desc.option_attrs self.setCaption(QtGui.QApplication.translate(self.__class__.__name__, opts.caption)) # was "Nanotube" self.heading_label.setText(QtGui.QApplication.translate(self.__class__.__name__, opts.title)) # was "Nanotube" self.sponsor_btn.setText(QString.null) self.done_btn.setText(QString.null) QToolTip.add(self.done_btn,QtGui.QApplication.translate(self.__class__.__name__, "Done")) self.abort_btn.setText(QString.null) QToolTip.add(self.abort_btn,QtGui.QApplication.translate(self.__class__.__name__, "Cancel")) self.preview_btn.setText(QString.null) QToolTip.add(self.preview_btn,QtGui.QApplication.translate(self.__class__.__name__, "Preview")) self.whatsthis_btn.setText(QString.null) QToolTip.add(self.whatsthis_btn,QtGui.QApplication.translate(self.__class__.__name__, "What's This Help")) ### move these up: ## if 0: ## self.parameters_grpbox.setTitle(QString.null) ## self.nt_parameters_grpbtn.setText(QString.null) ## self.parameters_grpbox_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Nanotube Parameters")) ## if 0: ## self.members_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Members :")) ## self.length_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Length :")) ## self.chirality_n_label.setText(QtGui.QApplication.translate(self.__class__.__name__, "Chirality (n) :")) ## self.members_combox.clear() ## self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, "C - C")) ## self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, "B - N")) ## self.length_linedit.setText(QtGui.QApplication.translate(self.__class__.__name__, "20.0 A")) ## self.chirality_N_spinbox.setSuffix(QString.null) self.cancel_btn.setText(QtGui.QApplication.translate(self.__class__.__name__, "Cancel")) self.ok_btn.setText(QtGui.QApplication.translate(self.__class__.__name__, "OK")) return pass # end of class parameter_dialog_or_frame -- maybe it should be renamed
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), (_('is undefined'), 'is undefined') ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating', 'datetime'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=sort_key): self.column_box.addItem(key, key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) return property(fget=fget, fset=fset) @property def current_val(self): ans = unicode(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v):k for k, v in lang_map().iteritems()} ans = rmap.get(lower(ans), ans) return ans @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return m = self.fm[col] dt = m['datatype'] action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
class RuleEditor(QDialog): # {{{ def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('coloring') else: self.rule_kind = 'icon' rule_text = _('icon') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle( _('Create/edit a column {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel( _('Create a column {0} rule by' ' filling in the boxes below'.format(rule_text))) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) else: self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'color': self.color_box = QComboBox(self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) else: self.filename_box = QComboBox() self.filename_box.setInsertPolicy( self.filename_box.InsertAlphabetically) d = os.path.join(config_dir, 'cc_icons') self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)): if icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) self.update_filename_box() l.addWidget(self.filename_box, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel( _('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, self.color_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda (k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key][ 'name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) self.update_color_label() self.color_box.currentIndexChanged.connect(self.update_color_label) else: self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def update_filename_box(self): self.filename_box.clear() self.icon_file_names.sort(key=sort_key) self.filename_box.addItem('') self.filename_box.addItems(self.icon_file_names) for i, filename in enumerate(self.icon_file_names): icon = QIcon(os.path.join(config_dir, 'cc_icons', filename)) self.filename_box.setItemIcon(i + 1, icon) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = unicode(self.color_box.currentText()) self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[('Images', ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = sanitize_file_name_unicode( os.path.splitext(os.path.basename(icon_path))[0] + '.png') if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) self.update_filename_box() try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = os.path.join(config_dir, 'cc_icons') if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.filename_box.setCurrentIndex( self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: idx = self.color_box.findText(rule.color) if idx >= 0: self.color_box.setCurrentIndex(idx) else: self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1) if rule.color: idx = self.filename_box.findText(rule.color) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = lower(unicode(self.filename_box.currentText())) if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>') % e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = unicode(self.filename_box.currentText()) else: r.color = unicode(self.color_box.currentText()) idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode( self.kind_box.itemData( self.kind_box.currentIndex()).toString()) else: kind = 'color' return kind, col, r
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool': (( _('is true'), 'is true', ), (_('is false'), 'is false'), (_('is undefined'), 'is undefined')), 'ondevice': ( ( _('is true'), 'is set', ), (_('is false'), 'is not set'), ), 'identifiers': ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int': ((_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt')), 'datetime': ((_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), (_('is more days ago than'), 'older count days'), (_('is fewer days ago than'), 'count_days'), (_('is more days from now than'), 'newer future days'), (_('is fewer days from now than'), 'older future days')), 'multiple': ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single': ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted(conditionable_columns(fm), key=lambda (key): sort_key(fm[key]['name'])): self.column_box.addItem(fm[key]['name'], key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(20) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found' % val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column' % val) return property(fget=fget, fset=fset) @property def current_val(self): ans = unicode(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v): k for k, v in lang_map().iteritems()} ans = rmap.get(lower(ans), ans) return ans @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': if action == 'count_days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _( 'Enter the maximum days old the item can be. Zero is today. ' 'Dates in the future always match') elif action == 'older count days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _( 'Enter the minimum days old the item can be. Zero is today. ' 'Dates in the future never match') elif action == 'older future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days in the future the item can be. ' 'Zero is today. Dates in the past always match') elif action == 'newer future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days in the future the item can be. ' 'Zero is today. Dates in the past never match') else: self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _( 'You can match multiple values by separating' ' them with %s') % m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
class RuleEditor(QDialog): # {{{ def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('coloring') else: self.rule_kind = 'icon' rule_text = _('icon') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a column {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a column {0} rule by' ' filling in the boxes below'.format(rule_text))) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) else: self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'color': self.color_box = QComboBox(self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) else: self.filename_box = QComboBox() self.filename_box.setInsertPolicy(self.filename_box.InsertAlphabetically) d = os.path.join(config_dir, 'cc_icons') self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)): if icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) self.update_filename_box() l.addWidget(self.filename_box, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, self.color_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) self.update_color_label() self.color_box.currentIndexChanged.connect(self.update_color_label) else: self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def update_filename_box(self): self.filename_box.clear() self.icon_file_names.sort(key=sort_key) self.filename_box.addItem('') self.filename_box.addItems(self.icon_file_names) for i,filename in enumerate(self.icon_file_names): icon = QIcon(os.path.join(config_dir, 'cc_icons', filename)) self.filename_box.setItemIcon(i+1, icon) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = unicode(self.color_box.currentText()) self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ ('Images', ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png') if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) self.update_filename_box() try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = os.path.join(config_dir, 'cc_icons') if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: idx = self.color_box.findText(rule.color) if idx >= 0: self.color_box.setCurrentIndex(idx) else: self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1) if rule.color: idx = self.filename_box.findText(rule.color) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = lower(unicode(self.filename_box.currentText())) if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = unicode(self.filename_box.currentText()) else: r.color = unicode(self.color_box.currentText()) idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode(self.kind_box.itemData( self.kind_box.currentIndex()).toString()) else: kind = 'color' return kind, col, r
class SourceSelectorDialog(QDialog): def __init__(self, parent, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.setModal(False) self.setWindowTitle("Select sources by...") lo = QVBoxLayout(self) lo.setMargin(10) lo.setSpacing(5) # select by lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) # lab = QLabel("Select:") # lo1.addWidget(lab) self.wselby = QComboBox(self) lo1.addWidget(self.wselby, 0) QObject.connect(self.wselby, SIGNAL("activated(const QString &)"), self._setup_selection_by) # under/over self.wgele = QComboBox(self) lo1.addWidget(self.wgele, 0) self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"]) QObject.connect(self.wgele, SIGNAL("activated(const QString &)"), self._select_threshold) # threshold value self.wthreshold = QLineEdit(self) QObject.connect(self.wthreshold, SIGNAL("editingFinished()"), self._select_threshold) lo1.addWidget(self.wthreshold, 1) # min and max label self.wminmax = QLabel(self) lo.addWidget(self.wminmax) # selection slider lo1 = QHBoxLayout() lo.addLayout(lo1) self.wpercent = QSlider(self) self.wpercent.setTracking(False) QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"), self._select_percentile) QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"), self._select_percentile_threshold) self.wpercent.setRange(0, 100) self.wpercent.setOrientation(Qt.Horizontal) lo1.addWidget(self.wpercent) self.wpercent_lbl = QLabel("0%", self) self.wpercent_lbl.setMinimumWidth(64) lo1.addWidget(self.wpercent_lbl) # # hide button # lo.addSpacing(10) # lo2 = QHBoxLayout() # lo.addLayout(lo2) # lo2.setContentsMargins(0,0,0,0) # hidebtn = QPushButton("Close",self) # hidebtn.setMinimumWidth(128) # QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide) # lo2.addStretch(1) # lo2.addWidget(hidebtn) # lo2.addStretch(1) # self.setMinimumWidth(384) self._in_select_threshold = False self._sort_index = None self.qerrmsg = QErrorMessage(self) def resetModel(self): """Resets dialog based on current model.""" if not self.model: return # getset of model tags, and remove the non-sorting tags alltags = set(self.model.tagnames) alltags -= NonSortingTags # make list of tags from StandardTags that are present in model self.sorttags = [tag for tag in StandardTags if tag in alltags or tag in TagAccessors] # append model tags that were not in StandardTags self.sorttags += list(alltags - set(self.sorttags)) # set selector self.wselby.clear() self.wselby.addItems(self.sorttags) for tag in "Iapp", "I": if tag in self.sorttags: self.wselby.setCurrentIndex(self.sorttags.index(tag)) break self._setup_selection_by(self.wselby.currentText()) def _reset_percentile(self): self.wthreshold.setText("") self.wpercent.setValue(50) self.wpercent_lbl.setText("--%") def _setup_selection_by(self, tag): tag = str(tag); # may be QString # clear threshold value and percentiles self._reset_percentile() # get min/max values, and sort indices # _sort_index will be an array of (value,src,cumsum) tuples, sorted by tag value (high to low), # where src is the source, and cumsum is the sum of all values in the list from 0 up to and including the current one self._sort_index = [] minval = maxval = None for isrc, src in enumerate(self.model.sources): try: if hasattr(src, tag): value = float(getattr(src, tag)) else: value = float(TagAccessors[tag](src)) # skip source if failed to access this tag as a float except: traceback.print_exc() continue self._sort_index.append([value, src, 0]) minval = min(minval, value) if minval is not None else value maxval = max(maxval, value) if maxval is not None else value # add label if minval is None: self._range = None self.wminmax.setText("<font color=red>'%s' is not a numeric attribute</font>" % tag) for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl: w.setEnabled(False) else: self._range = (minval, maxval) self.wminmax.setText("min: %g max: %g" % self._range) for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl: w.setEnabled(True) # sort index by descending values self._sort_index.sort(reverse=True) # generate cumulative sums cumsum = 0. for entry in self._sort_index: cumsum += entry[0] entry[2] = cumsum # Maps comparison operators to callables. Used in _select_threshold. # Each callable takes two arguments: e is a tuple of (value,src,cumsum) (see _sort_index above), and x is a threshold # Second argument is a flag: if False, selection is inverted w.r.t. operator Operators = { "<": ((lambda e, x: e[0] >= x), False), "<=": ((lambda e, x: e[0] > x), False), ">": ((lambda e, x: e[0] > x), True), ">=": ((lambda e, x: e[0] >= x), True), "sum<=": ((lambda e, x: e[2] <= x), True), "sum>": ((lambda e, x: e[2] <= x), False) } def _select_threshold(self, *dum): dprint(1, "select_threshold", dum) self._in_select_threshold = True busy = BusyIndicator() try: # get threshold, ignore if not set threshold = str(self.wthreshold.text()) if not threshold: self._reset_percentile() return # try to parse threshold, ignore if invalid try: threshold = float(threshold) except: self._reset_percentile() return # get comparison operator op, select = self.Operators[str(self.wgele.currentText())] # apply to initial segment (that matches operator) for num, entry in enumerate(self._sort_index): if not op(entry, threshold): break entry[1].selected = select else: num = len(self._sort_index) # apply to remaining segment for val, src, cumsum in self._sort_index[num:]: src.selected = not select # set percentile percent = round(float(num * 100) / len(self._sort_index)) if not select: percent = 100 - percent self.wpercent.setValue(percent) self.wpercent_lbl.setText("%3d%%" % percent) # emit signal self.model.emitSelection(self) finally: self._in_select_threshold = False busy = None def _select_percentile(self, percent): self._select_percentile_threshold(percent, do_select=True) def _select_percentile_threshold(self, percent, do_select=False): # ignore if no sort index set up, or if _select_threshold() is being called if self._sort_index is None or self._in_select_threshold: return dprint(1, "select_precentile_threshold", percent) busy = BusyIndicator() # number of objects to select nsrc = len(self._sort_index) nsel = int(math.ceil(nsrc * float(percent) / 100)) # get comparison operator opstr = str(self.wgele.currentText()) op, select = self.Operators[opstr] # select head or tail of list, depending on direction of operator if select: thr = self._sort_index[min(nsel, nsrc - 1)] slc1 = slice(0, nsel) slc2 = slice(nsel, None) else: thr = self._sort_index[-min(nsel + 1, nsrc)] slc1 = slice(nsrc - nsel, None) slc2 = slice(0, nsrc - nsel) if do_select: for val, src, cumsum in self._sort_index[slc1]: src.selected = True for val, src, cumsum in self._sort_index[slc2]: src.selected = False self.model.emitSelection(self) self.wpercent_lbl.setText("%3d%%" % percent) self.wthreshold.setText("%g" % (thr[2] if opstr.startswith("sum") else thr[0])) return nsel def setModel(self, model): """Sets the current model. If dialog is visible, applies the changes""" self.model = model if self.isVisible(): self.resetModel() if not model: self.hide() def show(self): """Shows dialog, resetting the model if it was invisible.""" if not self.isVisible(): self.resetModel() QDialog.show(self)
class AddTagDialog(QDialog): def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.setModal(modal) self.setWindowTitle("Add Tag") lo = QVBoxLayout(self) lo.setMargin(10) lo.setSpacing(5) # tag selector lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setSpacing(5) self.wtagsel = QComboBox(self) self.wtagsel.setEditable(True) wtagsel_lbl = QLabel("&Tag:", self) wtagsel_lbl.setBuddy(self.wtagsel) lo1.addWidget(wtagsel_lbl, 0) lo1.addWidget(self.wtagsel, 1) QObject.connect(self.wtagsel, SIGNAL("activated(int)"), self._check_tag) QObject.connect(self.wtagsel, SIGNAL("editTextChanged(const QString &)"), self._check_tag_text) # value editor self.valedit = ValueTypeEditor(self) lo.addWidget(self.valedit) # buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setMargin(5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) def setTags(self, tagnames): self.wtagsel.clear() self.wtagsel.addItems(list(tagnames)) self.wtagsel.addItem("") self.wtagsel.setCurrentIndex(len(tagnames)) def setValue(self, value): self.valedit.setValue(value) def _check_tag(self, tag): self.wokbtn.setEnabled(True) def _check_tag_text(self, text): self.wokbtn.setEnabled(bool(str(text) != "")) def accept(self): """When dialog is accepted with a default (bool) tag type, check if the user hasn't entered a name=value entry in the tag name field. This is a common mistake, and should be treated as a shortcut for setting string tags.""" if isinstance(self.valedit.getValue(), bool): tagval = str(self.wtagsel.currentText()).split("=", 1) if len(tagval) > 1: # print tagval if QMessageBox.warning( self, "Set a string tag instead?", """<P>You have included an "=" sign in the tag name. Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>""" % tuple(tagval), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.No: return self.wtagsel.setEditText(tagval[0]) self.valedit.setValue(tagval[1]) return QDialog.accept(self) def getTag(self): return str(self.wtagsel.currentText()), self.valedit.getValue()
class SourceSelectorDialog(QDialog): def __init__(self, parent, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.setModal(False) self.setWindowTitle("Select sources by...") lo = QVBoxLayout(self) lo.setMargin(10) lo.setSpacing(5) # select by lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) # lab = QLabel("Select:") # lo1.addWidget(lab) self.wselby = QComboBox(self) lo1.addWidget(self.wselby, 0) QObject.connect(self.wselby, SIGNAL("activated(const QString &)"), self._setup_selection_by) # under/over self.wgele = QComboBox(self) lo1.addWidget(self.wgele, 0) self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"]) QObject.connect(self.wgele, SIGNAL("activated(const QString &)"), self._select_threshold) # threshold value self.wthreshold = QLineEdit(self) QObject.connect(self.wthreshold, SIGNAL("editingFinished()"), self._select_threshold) lo1.addWidget(self.wthreshold, 1) # min and max label self.wminmax = QLabel(self) lo.addWidget(self.wminmax) # selection slider lo1 = QHBoxLayout() lo.addLayout(lo1) self.wpercent = QSlider(self) self.wpercent.setTracking(False) QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"), self._select_percentile) QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"), self._select_percentile_threshold) self.wpercent.setRange(0, 100) self.wpercent.setOrientation(Qt.Horizontal) lo1.addWidget(self.wpercent) self.wpercent_lbl = QLabel("0%", self) self.wpercent_lbl.setMinimumWidth(64) lo1.addWidget(self.wpercent_lbl) # # hide button # lo.addSpacing(10) # lo2 = QHBoxLayout() # lo.addLayout(lo2) # lo2.setContentsMargins(0,0,0,0) # hidebtn = QPushButton("Close",self) # hidebtn.setMinimumWidth(128) # QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide) # lo2.addStretch(1) # lo2.addWidget(hidebtn) # lo2.addStretch(1) # self.setMinimumWidth(384) self._in_select_threshold = False self._sort_index = None self.qerrmsg = QErrorMessage(self) def resetModel(self): """Resets dialog based on current model.""" if not self.model: return # getset of model tags, and remove the non-sorting tags alltags = set(self.model.tagnames) alltags -= NonSortingTags # make list of tags from StandardTags that are present in model self.sorttags = [ tag for tag in StandardTags if tag in alltags or tag in TagAccessors ] # append model tags that were not in StandardTags self.sorttags += list(alltags - set(self.sorttags)) # set selector self.wselby.clear() self.wselby.addItems(self.sorttags) for tag in "Iapp", "I": if tag in self.sorttags: self.wselby.setCurrentIndex(self.sorttags.index(tag)) break self._setup_selection_by(self.wselby.currentText()) def _reset_percentile(self): self.wthreshold.setText("") self.wpercent.setValue(50) self.wpercent_lbl.setText("--%") def _setup_selection_by(self, tag): tag = str(tag) # may be QString # clear threshold value and percentiles self._reset_percentile() # get min/max values, and sort indices # _sort_index will be an array of (value,src,cumsum) tuples, sorted by tag value (high to low), # where src is the source, and cumsum is the sum of all values in the list from 0 up to and including the current one self._sort_index = [] minval = maxval = None for isrc, src in enumerate(self.model.sources): try: if hasattr(src, tag): value = float(getattr(src, tag)) else: value = float(TagAccessors[tag](src)) # skip source if failed to access this tag as a float except: traceback.print_exc() continue self._sort_index.append([value, src, 0]) minval = min(minval, value) if minval is not None else value maxval = max(maxval, value) if maxval is not None else value # add label if minval is None: self._range = None self.wminmax.setText( "<font color=red>'%s' is not a numeric attribute</font>" % tag) for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl: w.setEnabled(False) else: self._range = (minval, maxval) self.wminmax.setText("min: %g max: %g" % self._range) for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl: w.setEnabled(True) # sort index by descending values self._sort_index.sort(reverse=True) # generate cumulative sums cumsum = 0. for entry in self._sort_index: cumsum += entry[0] entry[2] = cumsum # Maps comparison operators to callables. Used in _select_threshold. # Each callable takes two arguments: e is a tuple of (value,src,cumsum) (see _sort_index above), and x is a threshold # Second argument is a flag: if False, selection is inverted w.r.t. operator Operators = { "<": ((lambda e, x: e[0] >= x), False), "<=": ((lambda e, x: e[0] > x), False), ">": ((lambda e, x: e[0] > x), True), ">=": ((lambda e, x: e[0] >= x), True), "sum<=": ((lambda e, x: e[2] <= x), True), "sum>": ((lambda e, x: e[2] <= x), False) } def _select_threshold(self, *dum): dprint(1, "select_threshold", dum) self._in_select_threshold = True busy = BusyIndicator() try: # get threshold, ignore if not set threshold = str(self.wthreshold.text()) if not threshold: self._reset_percentile() return # try to parse threshold, ignore if invalid try: threshold = float(threshold) except: self._reset_percentile() return # get comparison operator op, select = self.Operators[str(self.wgele.currentText())] # apply to initial segment (that matches operator) for num, entry in enumerate(self._sort_index): if not op(entry, threshold): break entry[1].selected = select else: num = len(self._sort_index) # apply to remaining segment for val, src, cumsum in self._sort_index[num:]: src.selected = not select # set percentile percent = round(float(num * 100) / len(self._sort_index)) if not select: percent = 100 - percent self.wpercent.setValue(percent) self.wpercent_lbl.setText("%3d%%" % percent) # emit signal self.model.emitSelection(self) finally: self._in_select_threshold = False busy = None def _select_percentile(self, percent): self._select_percentile_threshold(percent, do_select=True) def _select_percentile_threshold(self, percent, do_select=False): # ignore if no sort index set up, or if _select_threshold() is being called if self._sort_index is None or self._in_select_threshold: return dprint(1, "select_precentile_threshold", percent) busy = BusyIndicator() # number of objects to select nsrc = len(self._sort_index) nsel = int(math.ceil(nsrc * float(percent) / 100)) # get comparison operator opstr = str(self.wgele.currentText()) op, select = self.Operators[opstr] # select head or tail of list, depending on direction of operator if select: thr = self._sort_index[min(nsel, nsrc - 1)] slc1 = slice(0, nsel) slc2 = slice(nsel, None) else: thr = self._sort_index[-min(nsel + 1, nsrc)] slc1 = slice(nsrc - nsel, None) slc2 = slice(0, nsrc - nsel) if do_select: for val, src, cumsum in self._sort_index[slc1]: src.selected = True for val, src, cumsum in self._sort_index[slc2]: src.selected = False self.model.emitSelection(self) self.wpercent_lbl.setText("%3d%%" % percent) self.wthreshold.setText( "%g" % (thr[2] if opstr.startswith("sum") else thr[0])) return nsel def setModel(self, model): """Sets the current model. If dialog is visible, applies the changes""" self.model = model if self.isVisible(): self.resetModel() if not model: self.hide() def show(self): """Shows dialog, resetting the model if it was invisible.""" if not self.isVisible(): self.resetModel() QDialog.show(self)
class AddTagDialog(QDialog): def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.setModal(modal) self.setWindowTitle("Add Tag") lo = QVBoxLayout(self) lo.setMargin(10) lo.setSpacing(5) # tag selector lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setSpacing(5) self.wtagsel = QComboBox(self) self.wtagsel.setEditable(True) wtagsel_lbl = QLabel("&Tag:", self) wtagsel_lbl.setBuddy(self.wtagsel) lo1.addWidget(wtagsel_lbl, 0) lo1.addWidget(self.wtagsel, 1) QObject.connect(self.wtagsel, SIGNAL("activated(int)"), self._check_tag) QObject.connect(self.wtagsel, SIGNAL("editTextChanged(const QString &)"), self._check_tag_text) # value editor self.valedit = ValueTypeEditor(self) lo.addWidget(self.valedit) # buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setMargin(5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) def setTags(self, tagnames): self.wtagsel.clear() self.wtagsel.addItems(list(tagnames)) self.wtagsel.addItem("") self.wtagsel.setCurrentIndex(len(tagnames)) def setValue(self, value): self.valedit.setValue(value) def _check_tag(self, tag): self.wokbtn.setEnabled(True) def _check_tag_text(self, text): self.wokbtn.setEnabled(bool(str(text) != "")) def accept(self): """When dialog is accepted with a default (bool) tag type, check if the user hasn't entered a name=value entry in the tag name field. This is a common mistake, and should be treated as a shortcut for setting string tags.""" if isinstance(self.valedit.getValue(), bool): tagval = str(self.wtagsel.currentText()).split("=", 1) if len(tagval) > 1: # print tagval if QMessageBox.warning(self, "Set a string tag instead?", """<P>You have included an "=" sign in the tag name. Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>""" % tuple(tagval), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.No: return self.wtagsel.setEditText(tagval[0]) self.valedit.setValue(tagval[1]) return QDialog.accept(self) def getTag(self): return str(self.wtagsel.currentText()), self.valedit.getValue()
class TwitterGui(QWidget): URL_REGEX = re.compile(r'''((?:mailto:|ftp://|http://|https://)[^ <>'"{}|\\^`[\]]*)''') def __init__(self, parent, logger, db_conn, update_func, safe_conn): super(TwitterGui, self).__init__(parent) self._db_conn = db_conn self.logger = logger self._reply_to_id = 0 self._update_func = update_func self._list = None if get_settings().get_proxy(): u = urlparse.urlsplit(get_settings().get_proxy()) proxy = QNetworkProxy() proxy.setType(QNetworkProxy.HttpProxy) proxy.setHostName(u.hostname); proxy.setPort(u.port) QNetworkProxy.setApplicationProxy(proxy); self.msgview = QWebView(self) self.msgview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.msgview.linkClicked.connect(self.link_clicked) self.userCombo = QComboBox(self) self.userCombo.setEditable(True) self.userCombo.activated.connect(self.toggle_user_in_list) self.showButton = QPushButton(chr(94), self) self.showButton.setMaximumHeight(13) self.showButton.clicked.connect(self.show_hide_animation) self.post_field = QTextEdit(self) self.post_field.setMaximumHeight(50) self.post_field.textChanged.connect(self.text_changed) self.send_button = QPushButton("Post", self) self.send_button.clicked.connect(self.post_status_clicked) self.refresh_button = QPushButton("Refresh", self) self.refresh_button.clicked.connect(self._update_func) self.attach_button = QPushButton("Attach", self) self.attach_button.clicked.connect(lambda _ : self.set_status("Attach something")) self.lists_box = QComboBox(self) self.lists_box.currentIndexChanged.connect(self.list_changed) self.lists_box.setEditable(False) self.lists_box.addItems([u"Home"] + self._db_conn.get_lists()) self.statusLabel = QLabel("Status", self) self.charCounter = QLabel("0", self) self.gridw = QWidget(self) self.gridw.setContentsMargins(0, 0, 0, 0) gridlay = QGridLayout(self.gridw) gridlay.setContentsMargins(0, 0, 0, 0) gridlay.addWidget(self.post_field, 0, 0, 2, 1) gridlay.addWidget(self.attach_button, 0, 1, 1, 1) gridlay.addWidget(self.send_button, 1, 1, 1, 1) gridlay.addWidget(self.lists_box, 0, 2, 1, 1) gridlay.addWidget(self.refresh_button, 1, 2, 1, 1) gridlay.addWidget(self.statusLabel, 2, 0, 1, 1) gridlay.addWidget(self.charCounter, 2, 1, 1, 2) hlay = QVBoxLayout(self) hlay.addWidget(self.msgview) hlay.addWidget(self.userCombo) hlay.addWidget(self.showButton) hlay.addWidget(self.gridw) safe_conn.connect_home_timeline_updated(self.update_view) safe_conn.connect_twitter_loop_started(self.start_refresh_animation) safe_conn.connect_twitter_loop_stopped(self.stop_refresh_animation) safe_conn.connect_update_posted(self.enable_posting) safe_conn.connect_range_limit_exceeded(lambda _ : self.set_status("Range limit exceeded")) safe_conn.connect_not_authenticated(lambda _ : self.set_status("Authentication failed")) self.gridw.hide() self.update_view() self.set_status("Twitter plugin initialized") def enable_posting(self, q_id, m_id): if m_id>1: self.post_field.setText("") self.set_status("Tweet posted") else: self.set_status("Failed to post tweet, Error: " + str(abs(m_id))) self.post_field.setEnabled(True) def link_clicked(self, url): if not url.host(): if url.hasQueryItem("reply-to") and url.hasQueryItem("screen-name"): self._reply_to_id = long(convert_string(url.queryItemValue("reply-to"))) self.post_field.setPlainText("@"+convert_string(url.queryItemValue("screen-name"))+" ") self.set_status("Reply to @"+convert_string(url.queryItemValue("screen-name"))) else: self.logger.error("Unknown command from link: "+str(url.toString())) else: webbrowser.open(str(url.toString())) def list_changed(self, list_idx): if list_idx: self._list = convert_string(self.lists_box.currentText()) self.userCombo.clear() self.userCombo.addItems(self._db_conn.get_known_users()) self.userCombo.completer().setCompletionMode(QCompleter.PopupCompletion) self.userCombo.show() self.set_status(self._list) else: self.userCombo.hide() self._list = None self.update_view() def post_status_clicked(self): msg = unicode(self.post_field.toPlainText().toUtf8(), encoding="UTF-8") if msg: self._db_conn.insert_post_queue(msg, self._reply_to_id) self._reply_to_id = 0 self._update_func() self.post_field.setDisabled(True) def start_refresh_animation(self): self.refresh_button.setDisabled(True) def stop_refresh_animation(self): self.refresh_button.setEnabled(True) def show_hide_animation(self): if self.gridw.isHidden(): self.gridw.show() self.showButton.setText("v") else: self.gridw.hide() self.showButton.setText(chr(94)) def text_changed(self): count = len(self.post_field.toPlainText()) if count==0: self._reply_to_id = 0 if self._reply_to_id: self.charCounter.setText(str(count) + " - reply to ") else: self.charCounter.setText(str(count)) def toggle_user_in_list(self, _): user = convert_string(self.userCombo.currentText()) if user in self._db_conn.get_users_from_list(self._list): self._db_conn.delete_user_from_list(user, self._list) self.set_status("Removed user %s from list %s"%(user, self._list)) else: self._db_conn.add_user_to_list(user, self._list) self.set_status("Added user %s to list %s"%(user, self._list)) self.update_view() @pyqtSlot(object) def update_view(self, _ = None): template_file_path = os.path.join(get_settings().get_main_config_dir(),"tweet.thtml") tweets = self._db_conn.get_last_tweets(user_list = self._list) if len(tweets)==0: return 0 templ_text = '<div>\ <a href="http://twitter.com/$NAME$/status/$ID$">\ <img src="$IMAGE$" style="float: left; margin-right: 2px" alt="$NAME$" title="$NAME$"/>\ </a>\ <p>$TEXT$</p>\ <span><a href="http://twitter.com/$RT_USER$">$RT_USER$</a></span>\ <span style="float: right">$CREATE_TIME$ <a href="?retweet=$ID$">retweet</a> <a href="?reply-to=$ID$&screen-name=$NAME$">reply</a></span>\ </div>\ <hr style="clear: both" />\ ' if os.path.exists(template_file_path): t_file = open(template_file_path, "r") templ_text = t_file.read() t_file.close() txt = "" for t in tweets: """m_id, screen_name, user_image, create_time, message_text, retweeted_by""" text = self.URL_REGEX.sub(r'<a href="\1">\1</a>', t[4]) t_txt = templ_text.replace("$IMAGE$", t[2]).replace("$NAME$", t[1]) t_txt = t_txt.replace("$ID$", str(t[0])).replace("$TEXT$", text) t_txt = t_txt.replace("$CREATE_TIME$", self.humanReadableTime(t[3])) t_txt = t_txt.replace("$RT_USER$", t[5] if t[5] else "") txt += t_txt txt += "<p style=\"float:right\">Updated: %s</p>"%time.strftime("%H:%M") self.msgview.setHtml(txt) """helper:""" def set_status(self, status_text): self.statusLabel.setText(status_text) self.showButton.setToolTip(status_text) def humanReadableTime(self, post_time): fudge = 1.25 delta = long(time.time()) - long(post_time) if delta < (1 * fudge): return 'about a second ago' elif delta < (60 * (1 / fudge)): return 'about %d seconds ago' % (delta) elif delta < (60 * fudge): return 'about a minute ago' elif delta < (60 * 60 * (1 / fudge)): return 'about %d minutes ago' % (delta / 60) elif delta < (60 * 60 * fudge) or delta / (60 * 60) == 1: return 'about an hour ago' elif delta < (60 * 60 * 24 * (1 / fudge)): return 'about %d hours ago' % (delta / (60 * 60)) elif delta < (60 * 60 * 24 * fudge) or delta / (60 * 60 * 24) == 1: return 'about a day ago' else: return 'about %d days ago' % (delta / (60 * 60 * 24))
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), (_('is undefined'), 'is undefined') ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating', 'datetime'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=sort_key): self.column_box.addItem(key, key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) return property(fget=fget, fset=fset) @property def current_val(self): return unicode(self.value_box.text()).strip() @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return m = self.fm[col] dt = m['datatype'] action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)