예제 #1
0
    def __init__(self, parent=None):
        # needs to be defined before the base class is initialized or the
        # event filter won't work
        self._treePopupWidget = None

        super(XOrbRecordBox, self).__init__(parent)

        # define custom properties
        self._currentRecord = None  # only used while loading
        self._changedRecord = -1

        self._tableTypeName = ''
        self._tableLookupIndex = ''
        self._baseHints = ('', '')
        self._tableType = None
        self._order = None
        self._query = None
        self._iconMapper = None
        self._labelMapper = str
        self._required = True
        self._loaded = False
        self._showTreePopup = False
        self._autoInitialize = False
        self._threadEnabled = True
        self._specifiedColumns = None
        self._specifiedColumnsOnly = False

        # create threading options
        self._worker = XOrbLookupWorker()
        self._workerThread = QThread()
        self._worker.moveToThread(self._workerThread)
        self._worker.setBatched(False)
        self._workerThread.start()

        # create connections
        self.loadRequested.connect(self._worker.loadRecords)
        self.lineEdit().textEntered.connect(self.assignCurrentRecord)
        self.lineEdit().editingFinished.connect(self.emitCurrentRecordEdited)
        self.lineEdit().returnPressed.connect(self.emitCurrentRecordEdited)

        self._worker.loadingStarted.connect(self.markLoadingStarted)
        self._worker.loadingFinished.connect(self.markLoadingFinished)
        self._worker.loadedRecords.connect(self.addRecordsFromThread)

        self.currentIndexChanged.connect(self.emitCurrentRecordChanged)
        QApplication.instance().aboutToQuit.connect(self.__cleanupWorker)
예제 #2
0
 def worker(self):
     """
     Returns the worker object for loading records for this record box.
     
     :return     <XOrbLookupWorker>
     """
     if self._worker is None:
         self._worker = XOrbLookupWorker(self.isThreadEnabled())
         self._worker.setBatchSize(self._batchSize)
         self._worker.setBatched(not self.isThreadEnabled())
         
         # connect the worker
         self.loadRequested.connect(self._worker.loadRecords)
         self._worker.loadingStarted.connect(self.markLoadingStarted)
         self._worker.loadingFinished.connect(self.markLoadingFinished)
         self._worker.loadedRecords.connect(self.addRecordsFromThread)
     
     return self._worker
예제 #3
0
 def __init__(self, parent=None):
     # needs to be defined before the base class is initialized or the
     # event filter won't work
     self._treePopupWidget   = None
     
     super(XOrbRecordBox, self).__init__( parent )
     
     # define custom properties
     self._currentRecord     = None # only used while loading
     self._changedRecord     = -1
     
     self._tableTypeName     = ''
     self._tableLookupIndex  = ''
     self._baseHints         = ('', '')
     self._tableType         = None
     self._order             = None
     self._query             = None
     self._iconMapper        = None
     self._labelMapper       = str
     self._required          = True
     self._loaded            = False
     self._showTreePopup     = False
     self._autoInitialize    = False
     self._threadEnabled     = True
     self._specifiedColumns  = None
     self._specifiedColumnsOnly = False
     
     # create threading options
     self._worker            = XOrbLookupWorker()
     self._workerThread      = QThread()
     self._worker.moveToThread(self._workerThread)
     self._worker.setBatched(False)
     self._workerThread.start()
     
     # create connections
     self.loadRequested.connect(self._worker.loadRecords)
     self.lineEdit().textEntered.connect(self.assignCurrentRecord)
     self.lineEdit().editingFinished.connect(self.emitCurrentRecordEdited)
     self.lineEdit().returnPressed.connect(self.emitCurrentRecordEdited)
     
     self._worker.loadingStarted.connect(self.markLoadingStarted)
     self._worker.loadingFinished.connect(self.markLoadingFinished)
     self._worker.loadedRecords.connect(self.addRecordsFromThread)
     
     self.currentIndexChanged.connect(self.emitCurrentRecordChanged)
     QApplication.instance().aboutToQuit.connect(self.__cleanupWorker)
예제 #4
0
class XOrbRecordBox(XComboBox):
    __designer_group__ = 'ProjexUI - ORB'
    
    """ Defines a combo box that contains records from the ORB system. """
    loadRequested = Signal(object)
    
    loadingStarted = Signal()
    loadingFinished = Signal()
    currentRecordChanged = Signal(object)
    currentRecordEdited = Signal(object)
    initialized = Signal()
    
    def __init__(self, parent=None):
        # needs to be defined before the base class is initialized or the
        # event filter won't work
        self._treePopupWidget   = None
        
        super(XOrbRecordBox, self).__init__( parent )
        
        # define custom properties
        self._currentRecord     = None # only used while loading
        self._changedRecord     = -1
        
        self._tableTypeName     = ''
        self._tableLookupIndex  = ''
        self._baseHints         = ('', '')
        self._tableType         = None
        self._order             = None
        self._query             = None
        self._iconMapper        = None
        self._labelMapper       = str
        self._required          = True
        self._loaded            = False
        self._showTreePopup     = False
        self._autoInitialize    = False
        self._threadEnabled     = True
        self._specifiedColumns  = None
        self._specifiedColumnsOnly = False
        
        # create threading options
        self._worker            = XOrbLookupWorker()
        self._workerThread      = QThread()
        self._worker.moveToThread(self._workerThread)
        self._worker.setBatched(False)
        self._workerThread.start()
        
        # create connections
        self.loadRequested.connect(self._worker.loadRecords)
        self.lineEdit().textEntered.connect(self.assignCurrentRecord)
        self.lineEdit().editingFinished.connect(self.emitCurrentRecordEdited)
        self.lineEdit().returnPressed.connect(self.emitCurrentRecordEdited)
        
        self._worker.loadingStarted.connect(self.markLoadingStarted)
        self._worker.loadingFinished.connect(self.markLoadingFinished)
        self._worker.loadedRecords.connect(self.addRecordsFromThread)
        
        self.currentIndexChanged.connect(self.emitCurrentRecordChanged)
        QApplication.instance().aboutToQuit.connect(self.__cleanupWorker)
    
    def __del__(self):
        self.__cleanupWorker()
    
    def __cleanupWorker(self):
        if not self._workerThread:
            return
        
        thread = self._workerThread
        worker = self._worker
        
        self._workerThread = None
        self._worker = None
        
        worker.deleteLater()
        
        thread.finished.connect(thread.deleteLater)
        thread.quit()
        thread.wait()
    
    def addRecord(self, record):
        """
        Adds the given record to the system.
        
        :param      record | <str>
        """
        label_mapper    = self.labelMapper()
        icon_mapper     = self.iconMapper()
        
        self.addItem(label_mapper(record))
        self.setItemData(self.count() - 1, wrapVariant(record), Qt.UserRole)
        
        # load icon
        if icon_mapper:
            self.setItemIcon(self.count() - 1, icon_mapper(record))
        
        if self.showTreePopup():
            XOrbRecordItem(self.treePopupWidget(), record)
    
    def addRecords(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper    = self.labelMapper()
        icon_mapper     = self.iconMapper()
        
        # create the items to display
        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.blockSignals(True)
            tree.setUpdatesEnabled(False)
        
        # add the items to the list
        start = self.count()
        self.addItems(map(label_mapper, records))
        
        # update the item information
        for i, record in enumerate(records):
            index = start + i
            
            self.setItemData(index, wrapVariant(record), Qt.UserRole)
            
            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))
            
            if tree:
                XOrbRecordItem(tree, record)
        
        if tree:
            tree.blockSignals(False)
            tree.setUpdatesEnabled(True)
    
    def addRecordsFromThread(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper    = self.labelMapper()
        icon_mapper     = self.iconMapper()
        
        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()
        
        # add the items to the list
        start = self.count()
        
        # update the item information
        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i, record in enumerate(records):
            index = start + i
            self.addItem(label_mapper(record))
            self.setItemData(index, wrapVariant(record), Qt.UserRole)
            
            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))
            
            if record == self._currentRecord:
                self.setCurrentIndex(self.count() - 1)
            
            if tree:
                XOrbRecordItem(tree, record)
        self.blockSignals(blocked)
    
    def acceptRecord(self, item):
        """
        Closes the tree popup and sets the current record.
        
        :param      record | <orb.Table>
        """
        record = item.record()
        self.treePopupWidget().close()
        self.setCurrentRecord(record)
    
    def assignCurrentRecord(self, text):
        """
        Assigns the current record from the inputed text.
        
        :param      text | <str>
        """
        if self.showTreePopup():
            item = self._treePopupWidget.currentItem()
            if item:
                self._currentRecord = item.record()
            else:
                self._currentRecord = None
            return
        
        # look up the record for the given text
        if text:
            index = self.findText(text)
        elif self.isRequired():
            index = 0
        else:
            index = -1
        
        # determine new record to look for
        record = self.recordAt(index)
        if record == self._currentRecord:
            return
        
        # set the current index and record for any changes
        self._currentRecord = record
        self.setCurrentIndex(index)
    
    def autoInitialize(self):
        """
        Returns whether or not this record box should auto-initialize its
        records.
        
        :return     <bool>
        """
        return self._autoInitialize
    
    def batchSize(self):
        """
        Returns the batch size to use when processing this record box's list
        of entries.
        
        :return     <int>
        """
        return self._worker.batchSize()
    
    def checkedRecords( self ):
        """
        Returns a list of the checked records from this combo box.
        
        :return     [<orb.Table>, ..]
        """
        indexes = self.checkedIndexes()
        return map(self.recordAt, indexes)
    
    def currentRecord( self ):
        """
        Returns the record found at the current index for this combo box.
        
        :rerturn        <orb.Table> || None
        """
        if self._currentRecord is None and self.isRequired():
            self._currentRecord = self.recordAt(self.currentIndex())
        return self._currentRecord
    
    def dragEnterEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return
        
        super(XOrbRecordBox, self).dragEnterEvent(event)
    
    def dragMoveEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return
        
        super(XOrbRecordBox, self).dragMoveEvent(event)
    
    def dropEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDropEvent>
        """
        # overload the current filtering options
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                data = str(data.data('application/x-orb-query'))
                query = Q.fromXmlString(data)
                self.setQuery(query)
                return
        
        elif self.tableType() and data.hasFormat('application/x-orb-records'):
            from projexui.widgets.xorbtreewidget import XOrbTreeWidget
            records = XOrbTreeWidget.dataRestoreRecords(data)
            
            for record in records:
                if isinstance(record, self.tableType()):
                    self.setCurrentRecord(record)
                    return
        
        super(XOrbRecordBox, self).dropEvent(event)
    
    def emitCurrentRecordChanged(self):
        """
        Emits the current record changed signal for this combobox, provided \
        the signals aren't blocked.
        """
        record = unwrapVariant(self.itemData(self.currentIndex(), Qt.UserRole))
        if not Table.recordcheck(record):
            record = None
        
        self._currentRecord = record
        if not self.signalsBlocked():
            self._changedRecord = record
            self.currentRecordChanged.emit(record)
    
    def emitCurrentRecordEdited(self):
        """
        Emits the current record edited signal for this combobox, provided the
        signals aren't blocked and the record has changed since the last time.
        """
        if self._changedRecord == -1:
            return
        
        if self.signalsBlocked():
            return
        
        record = self._changedRecord
        self._changedRecord = -1
        self.currentRecordEdited.emit(record)
    
    def eventFilter(self, object, event):
        """
        Filters events for the popup tree widget.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :retuen     <bool> | consumed
        """
        if not (object and object == self._treePopupWidget):
            return super(XOrbRecordBox, self).eventFilter(object, event)
        
        elif event.type() == event.KeyPress:
            # accept lookup
            if event.key() in (Qt.Key_Enter,
                               Qt.Key_Return,
                               Qt.Key_Tab,
                               Qt.Key_Backtab):
                
                item = object.currentItem()
                text = self.lineEdit().text()
                
                if not text:
                    record = None
                    item = None
                
                elif isinstance(item, XOrbRecordItem):
                    record = item.record()
                
                if record and item.isSelected() and not item.isHidden():
                    self.hidePopup()
                    self.setCurrentRecord(record)
                    event.accept()
                    return True
                
                else:
                    self.setCurrentRecord(None)
                    self.hidePopup()
                    self.lineEdit().setText(text)
                    self.lineEdit().keyPressEvent(event)
                    event.accept()
                    return True
                
            # cancel lookup
            elif event.key() == Qt.Key_Escape:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True
            
            # update the search info
            else:
                self.lineEdit().keyPressEvent(event)
        
        elif event.type() == event.Show:
            object.resizeToContents()
            object.horizontalScrollBar().setValue(0)
            
        elif event.type() == event.KeyRelease:
            self.lineEdit().keyReleaseEvent(event)
        
        elif event.type() == event.MouseButtonPress:
            local_pos = object.mapFromGlobal(event.globalPos())
            in_widget = object.rect().contains(local_pos)
            
            if not in_widget:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True
            
        return super(XOrbRecordBox, self).eventFilter(object, event)
    
    def focusNextChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())
        
        return super(XOrbRecordBox, self).focusNextChild(event)
    
    def focusNextPrevChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())
        
        return super(XOrbRecordBox, self).focusNextPrevChild(event)
    
    def focusInEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        self._changedRecord = -1
        super(XOrbRecordBox, self).focusInEvent(event)
    
    def focusOutEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())
        
        super(XOrbRecordBox, self).focusOutEvent(event)
    
    def hidePopup(self):
        """
        Overloads the hide popup method to handle when the user hides
        the popup widget.
        """
        if self._treePopupWidget and self.showTreePopup():
            self._treePopupWidget.close()
        
        super(XOrbRecordBox, self).hidePopup()
    
    def iconMapper( self ):
        """
        Returns the icon mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._iconMapper
    
    def isLoading(self):
        """
        Returns whether or not this combobox is loading records.
        
        :return     <bool>
        """
        return self._worker.isRunning()
    
    def isRequired( self ):
        """
        Returns whether or not this combo box requires the user to pick a
        selection.
        
        :return     <bool>
        """
        return self._required
    
    def isThreadEnabled(self):
        """
        Returns whether or not threading is enabled for this combo box.
        
        :return     <bool>
        """
        return self._threadEnabled
    
    def labelMapper( self ):
        """
        Returns the label mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._labelMapper
    
    @Slot(object)
    def lookupRecords(self, record):
        """
        Lookups records based on the inputed record.  This will use the 
        tableLookupIndex property to determine the Orb Index method to
        use to look up records.  That index method should take the inputed
        record as an argument, and return a list of records.
        
        :param      record | <orb.Table>
        """
        table_type = self.tableType()
        if not table_type:
            return
        
        index = getattr(table_type, self.tableLookupIndex(), None)
        if not index:
            return
        
        self.setRecords(index(record))
    
    def markLoadingStarted(self):
        """
        Marks this widget as loading records.
        """
        if self.isThreadEnabled():
            XLoaderWidget.start(self)
        
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setCursor(Qt.WaitCursor)
            tree.clear()
            tree.setUpdatesEnabled(False)
            tree.blockSignals(True)
            
            self._baseHints = (self.hint(), tree.hint())
            tree.setHint('Loading records...')
            self.setHint('Loading records...')
        else:
            self._baseHints = (self.hint(), '')
            self.setHint('Loading records...')
        
        self.setCursor(Qt.WaitCursor)
        self.blockSignals(True)
        self.setUpdatesEnabled(False)
        
        # prepare to load
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')
        
        self.loadingStarted.emit()
    
    def markLoadingFinished(self):
        """
        Marks this widget as finished loading records.
        """
        XLoaderWidget.stop(self, force=True)
        
        hint, tree_hint = self._baseHints
        self.setHint(hint)
        
        # set the tree widget
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setHint(tree_hint)
            tree.unsetCursor()
            tree.setUpdatesEnabled(True)
            tree.blockSignals(False)
        
        self.unsetCursor()
        self.blockSignals(False)
        self.setUpdatesEnabled(True)
        self.loadingFinished.emit()
    
    def order(self):
        """
        Returns the ordering for this widget.
        
        :return     [(<str> column, <str> asc|desc, ..] || None
        """
        return self._order
    
    def query( self ):
        """
        Returns the query used when querying the database for the records.
        
        :return     <Query> || None
        """
        return self._query
    
    def records( self ):
        """
        Returns the record list that ist linked with this combo box.
        
        :return     [<orb.Table>, ..]
        """
        records = []
        for i in range(self.count()):
            record = self.recordAt(i)
            if record:
                records.append(record)
        return records
    
    def recordAt(self, index):
        """
        Returns the record at the inputed index.
        
        :return     <orb.Table> || None
        """
        return unwrapVariant(self.itemData(index, Qt.UserRole))
    
    def refresh(self, records):
        """
        Refreshs the current user interface to match the latest settings.
        """
        self._loaded = True
        
        if self.isLoading():
            return
        
        # load the information
        if RecordSet.typecheck(records):
            table = records.table()
            self.setTableType(table)
            
            if self.order():
                records.setOrder(self.order())
            
            # load specific data for this record box
            if self.specifiedColumnsOnly():
                records.setColumns(map(lambda x: x.name(),
                                       self.specifiedColumns()))
            
            # load the records asynchronously
            if self.isThreadEnabled() and \
               table and \
               table.getDatabase().isThreadEnabled():
                # assign ordering based on tree table
                if self.showTreePopup():
                    tree = self.treePopupWidget()
                    if tree.isSortingEnabled():
                        col = tree.sortColumn()
                        colname = tree.headerItem().text(col)
                        column = table.schema().column(colname)
                        
                        if column:
                            if tree.sortOrder() == Qt.AscendingOrder:
                                sort_order = 'asc'
                            else:
                                sort_order = 'desc'
                            
                            records.setOrder([(column.name(), sort_order)])
                
                self.loadRequested.emit(records)
                return
        
        # load the records synchronously
        self.loadingStarted.emit()
        curr_record = self.currentRecord()
        self.blockSignals(True)
        self.setUpdatesEnabled(False)
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')
        self.addRecords(records)
        self.setUpdatesEnabled(True)
        self.blockSignals(False)
        self.setCurrentRecord(curr_record)
        self.loadingFinished.emit()
    
    def setAutoInitialize(self, state):
        """
        Sets whether or not this combo box should auto initialize itself
        when it is shown.
        
        :param      state | <bool>
        """
        self._autoInitialize = state
    
    def setBatchSize(self, size):
        """
        Sets the batch size of records to look up for this record box.
        
        :param      size | <int>
        """
        self._worker.setBatchSize(size)
    
    def setCheckedRecords( self, records ):
        """
        Sets the checked off records to the list of inputed records.
        
        :param      records | [<orb.Table>, ..]
        """
        QApplication.sendPostedEvents(self, -1)
        indexes = []
        
        for i in range(self.count()):
            record = self.recordAt(i)
            if record is not None and record in records:
                indexes.append(i)
        
        self.setCheckedIndexes(indexes)
    
    def setCurrentRecord(self, record, autoAdd=False):
        """
        Sets the index for this combobox to the inputed record instance.
        
        :param      record      <orb.Table>
        
        :return     <bool> success
        """
        if record is not None and not Table.recordcheck(record):
            return False
        
        # don't reassign the current record
        # clear the record
        if record is None:
            self._currentRecord = None
            blocked = self.signalsBlocked()
            self.blockSignals(True)
            self.setCurrentIndex(-1)
            self.blockSignals(blocked)
            
            if not blocked:
                self.currentRecordChanged.emit(None)
            
            return True
        
        elif record == self.currentRecord():
            return False
        
        self._currentRecord = record
        found = False
        
        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i in range(self.count()):
            stored = unwrapVariant(self.itemData(i, Qt.UserRole))
            if stored == record:
                self.setCurrentIndex(i)
                found = True
                break
        
        if not found and autoAdd:
            self.addRecord(record)
            self.setCurrentIndex(self.count() - 1)
        
        self.blockSignals(blocked)
        
        if not blocked:
            self.currentRecordChanged.emit(record)
        return False
    
    def setIconMapper( self, mapper ):
        """
        Sets the icon mapping method for this combobox to the inputed mapper. \
        The inputed mapper method should take a orb.Table instance as input \
        and return a QIcon as output.
        
        :param      mapper | <method> || None
        """
        self._iconMapper = mapper
    
    def setLabelMapper( self, mapper ):
        """
        Sets the label mapping method for this combobox to the inputed mapper.\
        The inputed mapper method should take a orb.Table instance as input \
        and return a string as output.
        
        :param      mapper | <method>
        """
        self._labelMapper = mapper
    
    def setOrder(self, order):
        """
        Sets the order for this combo box to the inputed order.  This will
        be used in conjunction with the query when loading records to the
        combobox.
        
        :param      order | [(<str> column, <str> asc|desc), ..] || None
        """
        self._order = order
    
    def setQuery(self, query, autoRefresh=True):
        """
        Sets the query for this record box for generating records.
        
        :param      query | <Query> || None
        """
        self._query = query
        
        tableType = self.tableType()
        if not tableType:
            return False
        
        if autoRefresh:
            self.refresh(tableType.select(where = query))
        
        return True
    
    def setRecords(self, records):
        """
        Sets the records on this combobox to the inputed record list.
        
        :param      records | [<orb.Table>, ..]
        """
        self.refresh(records)
    
    def setRequired( self, state ):
        """
        Sets the required state for this combo box.  If the column is not
        required, a blank record will be included with the choices.
        
        :param      state | <bool>
        """
        self._required = state
    
    def setShowTreePopup(self, state):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :param      state | <bool>
        """
        self._showTreePopup = state
    
    def setSpecifiedColumns(self, columns):
        """
        Sets the specified columns for this combobox widget.
        
        :param      columns | [<orb.Column>, ..] || [<str>, ..] || None
        """
        self._specifiedColumns = columns
        self._specifiedColumnsOnly = columns is not None
    
    def setSpecifiedColumnsOnly(self, state):
        """
        Sets whether or not only specified columns should be
        loaded for this record box.
        
        :param      state | <bool>
        """
        self._specifiedColumnsOnly = state
    
    def setTableLookupIndex(self, index):
        """
        Sets the name of the index method that will be used to lookup
        records for this combo box.
        
        :param    index | <str>
        """
        self._tableLookupIndex = str(index)
    
    def setTableType( self, tableType ):
        """
        Sets the table type for this record box to the inputed table type.
        
        :param      tableType | <orb.Table>
        """
        self._tableType     = tableType
        
        if tableType:
            self._tableTypeName = tableType.schema().name()
        else:
            self._tableTypeName = ''
    
    def setTableTypeName(self, name):
        """
        Sets the table type name for this record box to the inputed name.
        
        :param      name | <str>
        """
        self._tableTypeName = str(name)
        self._tableType = None
    
    def setThreadEnabled(self, state):
        """
        Sets whether or not threading should be enabled for this widget.  
        Actual threading will be determined by both this property, and whether
        or not the active ORB backend supports threading.
        
        :param      state | <bool>
        """
        self._threadEnabled = state
    
    def setVisible(self, state):
        """
        Sets the visibility for this record box.
        
        :param      state | <bool>
        """
        super(XOrbRecordBox, self).setVisible(state)
        
        if state and not self._loaded:
            if self.autoInitialize():
                table = self.tableType()
                if not table:
                    return
                
                self.setRecords(table.select(where=self.query()))
            else:
                self.initialized.emit()
    
    def showPopup(self):
        """
        Overloads the popup method from QComboBox to display an ORB tree widget
        when necessary.
        
        :sa     setShowTreePopup
        """
        if not self.showTreePopup():
            return super(XOrbRecordBox, self).showPopup()
        
        tree = self.treePopupWidget()
        
        if tree and not tree.isVisible():
            tree.move(self.mapToGlobal(QPoint(0, self.height())))
            tree.resize(self.width(), 250)
            tree.resizeToContents()
            tree.filterItems('')
            tree.setFilteredColumns(range(tree.columnCount()))
            tree.show()
    
    def showTreePopup(self):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :return     <bool>
        """
        return self._showTreePopup
    
    def specifiedColumns(self):
        """
        Returns the list of columns that are specified based on the column
        view for this widget.
        
        :return     [<orb.Column>, ..]
        """
        columns = []
        table = self.tableType()
        tree = self.treePopupWidget()
        schema = table.schema()
        
        if self._specifiedColumns is not None:
            colnames = self._specifiedColumns
        else:
            colnames = tree.columns()
        
        for colname in colnames:
            if isinstance(colname, Column):
                columns.append(colname)
            else:
                col = schema.column(colname)
                if col and not col.isProxy():
                    columns.append(col)
        
        return columns
        
    def specifiedColumnsOnly(self):
        """
        Returns whether or not only specified columns should be loaded
        for this record box.
        
        :return     <int>
        """
        return self._specifiedColumnsOnly
    
    def tableLookupIndex(self):
        """
        Returns the name of the index method that will be used to lookup
        records for this combo box.
        
        :return     <str>
        """
        return self._tableLookupIndex
    
    def tableType( self ):
        """
        Returns the table type for this instance.
        
        :return     <subclass of orb.Table> || None
        """
        if not self._tableType:
            if self._tableTypeName:
                self._tableType = Orb.instance().model(str(self._tableTypeName))
            
        return self._tableType
    
    def tableTypeName(self):
        """
        Returns the table type name that is set for this combo box.
        
        :return     <str>
        """
        return self._tableTypeName
    
    def treePopupWidget(self):
        """
        Returns the popup widget for this record box when it is supposed to
        be an ORB tree widget.
        
        :return     <XTreeWidget>
        """
        if not self._treePopupWidget:
            # create the treewidget
            tree = XTreeWidget(self)
            tree.setWindowFlags(Qt.Popup)
            tree.setFocusPolicy(Qt.StrongFocus)
            tree.installEventFilter(self)
            tree.setAlternatingRowColors(True)
            tree.setShowGridColumns(False)
            tree.setRootIsDecorated(False)
            tree.setVerticalScrollMode(tree.ScrollPerPixel)
            
            # create connections
            tree.itemClicked.connect(self.acceptRecord)
            
            self.lineEdit().textEdited.connect(tree.filterItems)
            self.lineEdit().textEdited.connect(self.showPopup)
            
            self._treePopupWidget = tree
        
        return self._treePopupWidget
    
    def worker(self):
        """
        Returns the worker object for loading records for this record box.
        
        :return     <XOrbLookupWorker>
        """
        return self._worker
    
    x_batchSize         = Property(int, batchSize, setBatchSize)
    x_required          = Property(bool, isRequired, setRequired)
    x_tableTypeName     = Property(str, tableTypeName, setTableTypeName)
    x_tableLookupIndex  = Property(str, tableLookupIndex, setTableLookupIndex)
    x_showTreePopup     = Property(bool, showTreePopup, setShowTreePopup)
    x_threadEnabled     = Property(bool, isThreadEnabled, setThreadEnabled)
예제 #5
0
class XOrbRecordBox(XComboBox):
    __designer_group__ = 'ProjexUI - ORB'
    """ Defines a combo box that contains records from the ORB system. """
    loadRequested = Signal(object)

    loadingStarted = Signal()
    loadingFinished = Signal()
    currentRecordChanged = Signal(object)
    currentRecordEdited = Signal(object)
    initialized = Signal()

    def __init__(self, parent=None):
        # needs to be defined before the base class is initialized or the
        # event filter won't work
        self._treePopupWidget = None

        super(XOrbRecordBox, self).__init__(parent)

        # define custom properties
        self._currentRecord = None  # only used while loading
        self._changedRecord = -1

        self._tableTypeName = ''
        self._tableLookupIndex = ''
        self._baseHints = ('', '')
        self._tableType = None
        self._order = None
        self._query = None
        self._iconMapper = None
        self._labelMapper = str
        self._required = True
        self._loaded = False
        self._showTreePopup = False
        self._autoInitialize = False
        self._threadEnabled = True
        self._specifiedColumns = None
        self._specifiedColumnsOnly = False

        # create threading options
        self._worker = XOrbLookupWorker()
        self._workerThread = QThread()
        self._worker.moveToThread(self._workerThread)
        self._worker.setBatched(False)
        self._workerThread.start()

        # create connections
        self.loadRequested.connect(self._worker.loadRecords)
        self.lineEdit().textEntered.connect(self.assignCurrentRecord)
        self.lineEdit().editingFinished.connect(self.emitCurrentRecordEdited)
        self.lineEdit().returnPressed.connect(self.emitCurrentRecordEdited)

        self._worker.loadingStarted.connect(self.markLoadingStarted)
        self._worker.loadingFinished.connect(self.markLoadingFinished)
        self._worker.loadedRecords.connect(self.addRecordsFromThread)

        self.currentIndexChanged.connect(self.emitCurrentRecordChanged)
        QApplication.instance().aboutToQuit.connect(self.__cleanupWorker)

    def __del__(self):
        self.__cleanupWorker()

    def __cleanupWorker(self):
        if not self._workerThread:
            return

        thread = self._workerThread
        worker = self._worker

        self._workerThread = None
        self._worker = None

        worker.deleteLater()

        thread.finished.connect(thread.deleteLater)
        thread.quit()
        thread.wait()

    def addRecord(self, record):
        """
        Adds the given record to the system.
        
        :param      record | <str>
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        self.addItem(label_mapper(record))
        self.setItemData(self.count() - 1, wrapVariant(record), Qt.UserRole)

        # load icon
        if icon_mapper:
            self.setItemIcon(self.count() - 1, icon_mapper(record))

        if self.showTreePopup():
            XOrbRecordItem(self.treePopupWidget(), record)

    def addRecords(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        # create the items to display
        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.blockSignals(True)
            tree.setUpdatesEnabled(False)

        # add the items to the list
        start = self.count()
        self.addItems(map(label_mapper, records))

        # update the item information
        for i, record in enumerate(records):
            index = start + i

            self.setItemData(index, wrapVariant(record), Qt.UserRole)

            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))

            if tree:
                XOrbRecordItem(tree, record)

        if tree:
            tree.blockSignals(False)
            tree.setUpdatesEnabled(True)

    def addRecordsFromThread(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()

        # add the items to the list
        start = self.count()

        # update the item information
        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i, record in enumerate(records):
            index = start + i
            self.addItem(label_mapper(record))
            self.setItemData(index, wrapVariant(record), Qt.UserRole)

            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))

            if record == self._currentRecord:
                self.setCurrentIndex(self.count() - 1)

            if tree:
                XOrbRecordItem(tree, record)
        self.blockSignals(blocked)

    def acceptRecord(self, item):
        """
        Closes the tree popup and sets the current record.
        
        :param      record | <orb.Table>
        """
        record = item.record()
        self.treePopupWidget().close()
        self.setCurrentRecord(record)

    def assignCurrentRecord(self, text):
        """
        Assigns the current record from the inputed text.
        
        :param      text | <str>
        """
        if self.showTreePopup():
            item = self._treePopupWidget.currentItem()
            if item:
                self._currentRecord = item.record()
            else:
                self._currentRecord = None
            return

        # look up the record for the given text
        if text:
            index = self.findText(text)
        elif self.isRequired():
            index = 0
        else:
            index = -1

        # determine new record to look for
        record = self.recordAt(index)
        if record == self._currentRecord:
            return

        # set the current index and record for any changes
        self._currentRecord = record
        self.setCurrentIndex(index)

    def autoInitialize(self):
        """
        Returns whether or not this record box should auto-initialize its
        records.
        
        :return     <bool>
        """
        return self._autoInitialize

    def batchSize(self):
        """
        Returns the batch size to use when processing this record box's list
        of entries.
        
        :return     <int>
        """
        return self._worker.batchSize()

    def checkedRecords(self):
        """
        Returns a list of the checked records from this combo box.
        
        :return     [<orb.Table>, ..]
        """
        indexes = self.checkedIndexes()
        return map(self.recordAt, indexes)

    def currentRecord(self):
        """
        Returns the record found at the current index for this combo box.
        
        :rerturn        <orb.Table> || None
        """
        if self._currentRecord is None and self.isRequired():
            self._currentRecord = self.recordAt(self.currentIndex())
        return self._currentRecord

    def dragEnterEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return

        super(XOrbRecordBox, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return

        super(XOrbRecordBox, self).dragMoveEvent(event)

    def dropEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDropEvent>
        """
        # overload the current filtering options
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                data = str(data.data('application/x-orb-query'))
                query = Q.fromXmlString(data)
                self.setQuery(query)
                return

        elif self.tableType() and data.hasFormat('application/x-orb-records'):
            from projexui.widgets.xorbtreewidget import XOrbTreeWidget
            records = XOrbTreeWidget.dataRestoreRecords(data)

            for record in records:
                if isinstance(record, self.tableType()):
                    self.setCurrentRecord(record)
                    return

        super(XOrbRecordBox, self).dropEvent(event)

    def emitCurrentRecordChanged(self):
        """
        Emits the current record changed signal for this combobox, provided \
        the signals aren't blocked.
        """
        record = unwrapVariant(self.itemData(self.currentIndex(), Qt.UserRole))
        if not Table.recordcheck(record):
            record = None

        self._currentRecord = record
        if not self.signalsBlocked():
            self._changedRecord = record
            self.currentRecordChanged.emit(record)

    def emitCurrentRecordEdited(self):
        """
        Emits the current record edited signal for this combobox, provided the
        signals aren't blocked and the record has changed since the last time.
        """
        if self._changedRecord == -1:
            return

        if self.signalsBlocked():
            return

        record = self._changedRecord
        self._changedRecord = -1
        self.currentRecordEdited.emit(record)

    def eventFilter(self, object, event):
        """
        Filters events for the popup tree widget.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :retuen     <bool> | consumed
        """
        if not (object and object == self._treePopupWidget):
            return super(XOrbRecordBox, self).eventFilter(object, event)

        elif event.type() == event.KeyPress:
            # accept lookup
            if event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab,
                               Qt.Key_Backtab):

                item = object.currentItem()
                text = self.lineEdit().text()

                if not text:
                    record = None
                    item = None

                elif isinstance(item, XOrbRecordItem):
                    record = item.record()

                if record and item.isSelected() and not item.isHidden():
                    self.hidePopup()
                    self.setCurrentRecord(record)
                    event.accept()
                    return True

                else:
                    self.setCurrentRecord(None)
                    self.hidePopup()
                    self.lineEdit().setText(text)
                    self.lineEdit().keyPressEvent(event)
                    event.accept()
                    return True

            # cancel lookup
            elif event.key() == Qt.Key_Escape:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True

            # update the search info
            else:
                self.lineEdit().keyPressEvent(event)

        elif event.type() == event.Show:
            object.resizeToContents()
            object.horizontalScrollBar().setValue(0)

        elif event.type() == event.KeyRelease:
            self.lineEdit().keyReleaseEvent(event)

        elif event.type() == event.MouseButtonPress:
            local_pos = object.mapFromGlobal(event.globalPos())
            in_widget = object.rect().contains(local_pos)

            if not in_widget:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True

        return super(XOrbRecordBox, self).eventFilter(object, event)

    def focusNextChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        return super(XOrbRecordBox, self).focusNextChild(event)

    def focusNextPrevChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        return super(XOrbRecordBox, self).focusNextPrevChild(event)

    def focusInEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        self._changedRecord = -1
        super(XOrbRecordBox, self).focusInEvent(event)

    def focusOutEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        super(XOrbRecordBox, self).focusOutEvent(event)

    def hidePopup(self):
        """
        Overloads the hide popup method to handle when the user hides
        the popup widget.
        """
        if self._treePopupWidget and self.showTreePopup():
            self._treePopupWidget.close()

        super(XOrbRecordBox, self).hidePopup()

    def iconMapper(self):
        """
        Returns the icon mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._iconMapper

    def isLoading(self):
        """
        Returns whether or not this combobox is loading records.
        
        :return     <bool>
        """
        return self._worker.isRunning()

    def isRequired(self):
        """
        Returns whether or not this combo box requires the user to pick a
        selection.
        
        :return     <bool>
        """
        return self._required

    def isThreadEnabled(self):
        """
        Returns whether or not threading is enabled for this combo box.
        
        :return     <bool>
        """
        return self._threadEnabled

    def labelMapper(self):
        """
        Returns the label mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._labelMapper

    @Slot(object)
    def lookupRecords(self, record):
        """
        Lookups records based on the inputed record.  This will use the 
        tableLookupIndex property to determine the Orb Index method to
        use to look up records.  That index method should take the inputed
        record as an argument, and return a list of records.
        
        :param      record | <orb.Table>
        """
        table_type = self.tableType()
        if not table_type:
            return

        index = getattr(table_type, self.tableLookupIndex(), None)
        if not index:
            return

        self.setRecords(index(record))

    def markLoadingStarted(self):
        """
        Marks this widget as loading records.
        """
        if self.isThreadEnabled():
            XLoaderWidget.start(self)

        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setCursor(Qt.WaitCursor)
            tree.clear()
            tree.setUpdatesEnabled(False)
            tree.blockSignals(True)

            self._baseHints = (self.hint(), tree.hint())
            tree.setHint('Loading records...')
            self.setHint('Loading records...')
        else:
            self._baseHints = (self.hint(), '')
            self.setHint('Loading records...')

        self.setCursor(Qt.WaitCursor)
        self.blockSignals(True)
        self.setUpdatesEnabled(False)

        # prepare to load
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')

        self.loadingStarted.emit()

    def markLoadingFinished(self):
        """
        Marks this widget as finished loading records.
        """
        XLoaderWidget.stop(self, force=True)

        hint, tree_hint = self._baseHints
        self.setHint(hint)

        # set the tree widget
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setHint(tree_hint)
            tree.unsetCursor()
            tree.setUpdatesEnabled(True)
            tree.blockSignals(False)

        self.unsetCursor()
        self.blockSignals(False)
        self.setUpdatesEnabled(True)
        self.loadingFinished.emit()

    def order(self):
        """
        Returns the ordering for this widget.
        
        :return     [(<str> column, <str> asc|desc, ..] || None
        """
        return self._order

    def query(self):
        """
        Returns the query used when querying the database for the records.
        
        :return     <Query> || None
        """
        return self._query

    def records(self):
        """
        Returns the record list that ist linked with this combo box.
        
        :return     [<orb.Table>, ..]
        """
        records = []
        for i in range(self.count()):
            record = self.recordAt(i)
            if record:
                records.append(record)
        return records

    def recordAt(self, index):
        """
        Returns the record at the inputed index.
        
        :return     <orb.Table> || None
        """
        return unwrapVariant(self.itemData(index, Qt.UserRole))

    def refresh(self, records):
        """
        Refreshs the current user interface to match the latest settings.
        """
        self._loaded = True

        if self.isLoading():
            return

        # load the information
        if RecordSet.typecheck(records):
            table = records.table()
            self.setTableType(table)

            if self.order():
                records.setOrder(self.order())

            # load specific data for this record box
            if self.specifiedColumnsOnly():
                records.setColumns(
                    map(lambda x: x.name(), self.specifiedColumns()))

            # load the records asynchronously
            if self.isThreadEnabled() and \
               table and \
               table.getDatabase().isThreadEnabled():
                # assign ordering based on tree table
                if self.showTreePopup():
                    tree = self.treePopupWidget()
                    if tree.isSortingEnabled():
                        col = tree.sortColumn()
                        colname = tree.headerItem().text(col)
                        column = table.schema().column(colname)

                        if column:
                            if tree.sortOrder() == Qt.AscendingOrder:
                                sort_order = 'asc'
                            else:
                                sort_order = 'desc'

                            records.setOrder([(column.name(), sort_order)])

                self.loadRequested.emit(records)
                return

        # load the records synchronously
        self.loadingStarted.emit()
        curr_record = self.currentRecord()
        self.blockSignals(True)
        self.setUpdatesEnabled(False)
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')
        self.addRecords(records)
        self.setUpdatesEnabled(True)
        self.blockSignals(False)
        self.setCurrentRecord(curr_record)
        self.loadingFinished.emit()

    def setAutoInitialize(self, state):
        """
        Sets whether or not this combo box should auto initialize itself
        when it is shown.
        
        :param      state | <bool>
        """
        self._autoInitialize = state

    def setBatchSize(self, size):
        """
        Sets the batch size of records to look up for this record box.
        
        :param      size | <int>
        """
        self._worker.setBatchSize(size)

    def setCheckedRecords(self, records):
        """
        Sets the checked off records to the list of inputed records.
        
        :param      records | [<orb.Table>, ..]
        """
        QApplication.sendPostedEvents(self, -1)
        indexes = []

        for i in range(self.count()):
            record = self.recordAt(i)
            if record is not None and record in records:
                indexes.append(i)

        self.setCheckedIndexes(indexes)

    def setCurrentRecord(self, record, autoAdd=False):
        """
        Sets the index for this combobox to the inputed record instance.
        
        :param      record      <orb.Table>
        
        :return     <bool> success
        """
        if record is not None and not Table.recordcheck(record):
            return False

        # don't reassign the current record
        # clear the record
        if record is None:
            self._currentRecord = None
            blocked = self.signalsBlocked()
            self.blockSignals(True)
            self.setCurrentIndex(-1)
            self.blockSignals(blocked)

            if not blocked:
                self.currentRecordChanged.emit(None)

            return True

        elif record == self.currentRecord():
            return False

        self._currentRecord = record
        found = False

        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i in range(self.count()):
            stored = unwrapVariant(self.itemData(i, Qt.UserRole))
            if stored == record:
                self.setCurrentIndex(i)
                found = True
                break

        if not found and autoAdd:
            self.addRecord(record)
            self.setCurrentIndex(self.count() - 1)

        self.blockSignals(blocked)

        if not blocked:
            self.currentRecordChanged.emit(record)
        return False

    def setIconMapper(self, mapper):
        """
        Sets the icon mapping method for this combobox to the inputed mapper. \
        The inputed mapper method should take a orb.Table instance as input \
        and return a QIcon as output.
        
        :param      mapper | <method> || None
        """
        self._iconMapper = mapper

    def setLabelMapper(self, mapper):
        """
        Sets the label mapping method for this combobox to the inputed mapper.\
        The inputed mapper method should take a orb.Table instance as input \
        and return a string as output.
        
        :param      mapper | <method>
        """
        self._labelMapper = mapper

    def setOrder(self, order):
        """
        Sets the order for this combo box to the inputed order.  This will
        be used in conjunction with the query when loading records to the
        combobox.
        
        :param      order | [(<str> column, <str> asc|desc), ..] || None
        """
        self._order = order

    def setQuery(self, query, autoRefresh=True):
        """
        Sets the query for this record box for generating records.
        
        :param      query | <Query> || None
        """
        self._query = query

        tableType = self.tableType()
        if not tableType:
            return False

        if autoRefresh:
            self.refresh(tableType.select(where=query))

        return True

    def setRecords(self, records):
        """
        Sets the records on this combobox to the inputed record list.
        
        :param      records | [<orb.Table>, ..]
        """
        self.refresh(records)

    def setRequired(self, state):
        """
        Sets the required state for this combo box.  If the column is not
        required, a blank record will be included with the choices.
        
        :param      state | <bool>
        """
        self._required = state

    def setShowTreePopup(self, state):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :param      state | <bool>
        """
        self._showTreePopup = state

    def setSpecifiedColumns(self, columns):
        """
        Sets the specified columns for this combobox widget.
        
        :param      columns | [<orb.Column>, ..] || [<str>, ..] || None
        """
        self._specifiedColumns = columns
        self._specifiedColumnsOnly = columns is not None

    def setSpecifiedColumnsOnly(self, state):
        """
        Sets whether or not only specified columns should be
        loaded for this record box.
        
        :param      state | <bool>
        """
        self._specifiedColumnsOnly = state

    def setTableLookupIndex(self, index):
        """
        Sets the name of the index method that will be used to lookup
        records for this combo box.
        
        :param    index | <str>
        """
        self._tableLookupIndex = str(index)

    def setTableType(self, tableType):
        """
        Sets the table type for this record box to the inputed table type.
        
        :param      tableType | <orb.Table>
        """
        self._tableType = tableType

        if tableType:
            self._tableTypeName = tableType.schema().name()
        else:
            self._tableTypeName = ''

    def setTableTypeName(self, name):
        """
        Sets the table type name for this record box to the inputed name.
        
        :param      name | <str>
        """
        self._tableTypeName = str(name)
        self._tableType = None

    def setThreadEnabled(self, state):
        """
        Sets whether or not threading should be enabled for this widget.  
        Actual threading will be determined by both this property, and whether
        or not the active ORB backend supports threading.
        
        :param      state | <bool>
        """
        self._threadEnabled = state

    def setVisible(self, state):
        """
        Sets the visibility for this record box.
        
        :param      state | <bool>
        """
        super(XOrbRecordBox, self).setVisible(state)

        if state and not self._loaded:
            if self.autoInitialize():
                table = self.tableType()
                if not table:
                    return

                self.setRecords(table.select(where=self.query()))
            else:
                self.initialized.emit()

    def showPopup(self):
        """
        Overloads the popup method from QComboBox to display an ORB tree widget
        when necessary.
        
        :sa     setShowTreePopup
        """
        if not self.showTreePopup():
            return super(XOrbRecordBox, self).showPopup()

        tree = self.treePopupWidget()

        if tree and not tree.isVisible():
            tree.move(self.mapToGlobal(QPoint(0, self.height())))
            tree.resize(self.width(), 250)
            tree.resizeToContents()
            tree.filterItems('')
            tree.setFilteredColumns(range(tree.columnCount()))
            tree.show()

    def showTreePopup(self):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :return     <bool>
        """
        return self._showTreePopup

    def specifiedColumns(self):
        """
        Returns the list of columns that are specified based on the column
        view for this widget.
        
        :return     [<orb.Column>, ..]
        """
        columns = []
        table = self.tableType()
        tree = self.treePopupWidget()
        schema = table.schema()

        if self._specifiedColumns is not None:
            colnames = self._specifiedColumns
        else:
            colnames = tree.columns()

        for colname in colnames:
            if isinstance(colname, Column):
                columns.append(colname)
            else:
                col = schema.column(colname)
                if col and not col.isProxy():
                    columns.append(col)

        return columns

    def specifiedColumnsOnly(self):
        """
        Returns whether or not only specified columns should be loaded
        for this record box.
        
        :return     <int>
        """
        return self._specifiedColumnsOnly

    def tableLookupIndex(self):
        """
        Returns the name of the index method that will be used to lookup
        records for this combo box.
        
        :return     <str>
        """
        return self._tableLookupIndex

    def tableType(self):
        """
        Returns the table type for this instance.
        
        :return     <subclass of orb.Table> || None
        """
        if not self._tableType:
            if self._tableTypeName:
                self._tableType = Orb.instance().model(str(
                    self._tableTypeName))

        return self._tableType

    def tableTypeName(self):
        """
        Returns the table type name that is set for this combo box.
        
        :return     <str>
        """
        return self._tableTypeName

    def treePopupWidget(self):
        """
        Returns the popup widget for this record box when it is supposed to
        be an ORB tree widget.
        
        :return     <XTreeWidget>
        """
        if not self._treePopupWidget:
            # create the treewidget
            tree = XTreeWidget(self)
            tree.setWindowFlags(Qt.Popup)
            tree.setFocusPolicy(Qt.StrongFocus)
            tree.installEventFilter(self)
            tree.setAlternatingRowColors(True)
            tree.setShowGridColumns(False)
            tree.setRootIsDecorated(False)
            tree.setVerticalScrollMode(tree.ScrollPerPixel)

            # create connections
            tree.itemClicked.connect(self.acceptRecord)

            self.lineEdit().textEdited.connect(tree.filterItems)
            self.lineEdit().textEdited.connect(self.showPopup)

            self._treePopupWidget = tree

        return self._treePopupWidget

    def worker(self):
        """
        Returns the worker object for loading records for this record box.
        
        :return     <XOrbLookupWorker>
        """
        return self._worker

    x_batchSize = Property(int, batchSize, setBatchSize)
    x_required = Property(bool, isRequired, setRequired)
    x_tableTypeName = Property(str, tableTypeName, setTableTypeName)
    x_tableLookupIndex = Property(str, tableLookupIndex, setTableLookupIndex)
    x_showTreePopup = Property(bool, showTreePopup, setShowTreePopup)
    x_threadEnabled = Property(bool, isThreadEnabled, setThreadEnabled)