def get_qapp(*args, **kwargs):
    """ Gets the global Qt application object. Creates one if it doesn't exist.
    """
    qApp = QtWidgets.QApplication.instance()
    if qApp:
        logger.debug("Returning existing QApplication")
        return qApp
    else:
        logger.debug("Creating new QApplication")
        return QtWidgets.QApplication(*args, **kwargs) def get_qsettings():
    """ Creates a QSettings object for this application.    
        We do not set the application and organization in the QApplication object to 
        prevent side-effects.
    """
    return QtCore.QSettings("titusjan.nl", PROGRAM_NAME) def start_qt_event_loop(qApp):
    """ Starts the eventloop if it's not yet running.

        If the IPython event loop is active (and set to Qt) this function does nothing. The IPython 
        event loop will process Qt events as well so the user can continue to use the command 
        prompt together with the ObjectBrower. Unfortunately this behaviour is broken again in
        IPython 5, so there we fall back on the non-interactive event loop.
    """
    if in_ipython():
        from IPython import version_info
        logger.debug("IPython detected. Version info: {}".format(version_info))
        if version_info[0] < 4:
            logger.debug("Event loop integration not supported for IPython < 4")
        elif version_info[0] == 5 and version_info[1] <= 1:
            # The is_event_loop_running_qt4 function is broken in IPython 5.0 and 5.1.
            # https://github.com/ipython/ipython/issues/9974
            logger.debug("Event loop integration does not work in IPython 5.0 and 5.1")
        else:
            try:
                from IPython.lib.guisupport import is_event_loop_running_qt4, start_event_loop_qt4
                if is_event_loop_running_qt4(qApp):
                    logger.info("IPython event loop already running. GUI integration possible.")
                else:
                    # No gui integration
                    logger.info("Starting (non-interactive) IPython event loop")
                    start_event_loop_qt4(qApp) # exit code always 0
                return
            except Exception as ex:
                logger.warning("Unable to start IPython Qt event loop: {}".format(ex))
                logger.warning("Falling back on non-interactive event loop: {}".format(ex))

    logger.info("Starting (non-interactive) event loop")
    return qApp.exec_() PROGRAM_NAME = 'objbrowser' PROGRAM_VERSION = '1.2.1' PROGRAM_URL = 'https://github.com/titusjan/objbrowser' DEBUGGING = False PYTHON_VERSION = "%d.%d.%d" % (sys.version_info[0:3]) QT_API_NAME = "<NOT-FOUND>" QT_API = "<NOT-FOUND>" QTPY_VERSION = "<NOT-FOUND>" class TreeProxyModel(QtCore.QSortFilterProxyModel):
    """ Proxy model that overrides the sorting and can filter out items
    """
    def __init__(self,
                 show_callable_attributes = True,
                 show_special_attributes = True,
                 parent = None):
        """ Constructor
        
            :param show_callable_attributes: if True the callables objects,
                i.e. objects (such as function) that  a __call__ method, 
                will be displayed (in brown). If False they are hidden.
            :param show_special_attributes: if True the objects special attributes, 
                i.e. methods with a name that starts and ends with two underscores, 
                will be displayed (in italics). If False they are hidden.
            :param parent: the parent widget
        """
        super(TreeProxyModel, self).__init__(parent)

        self._show_callables = show_callable_attributes
        self._show_special_attributes = show_special_attributes


    def treeItem(self, proxy_index):
        index = self.mapToSource(proxy_index)
        return self.sourceModel().treeItem(index)
    
    
    def firstItemIndex(self): 
        """ Returns the first child of the root item.
        """
        # We cannot just call the same function of the source model because the first node
        # there may be hidden.
        source_root_index = self.sourceModel().rootIndex()
        proxy_root_index = self.mapFromSource(source_root_index)
        first_item_index = self.index(0, 0, proxy_root_index)
        return first_item_index
            

    def filterAcceptsRow(self, sourceRow, sourceParentIndex):
        """ Returns true if the item in the row indicated by the given source_row and 
            source_parent should be included in the model.
        """
        parent_item = self.sourceModel().treeItem(sourceParentIndex)
        tree_item = parent_item.child(sourceRow)
        
        accept = ((self._show_special_attributes or not tree_item.is_special_attribute) and
                  (self._show_callables or not tree_item.is_callable_attribute))

        #logger.debug("filterAcceptsRow = {}: {}".format(accept, tree_item))
        return accept
    
    
    def getShowCallables(self):
        return self._show_callables

        
    def setShowCallables(self, show_callables):
        """ Shows/hides show_callables, which have a __call__ attribute.
            Repopulates the tree.
        """
        logger.debug("setShowCallables: {}".format(show_callables))
        self._show_callables = show_callables
        self.invalidateFilter()


    def getShowSpecialAttributes(self):
        return self._show_special_attributes
        
        
    def setShowSpecialAttributes(self, show_special_attributes):
        """ Shows/hides special attributes, which begin with an underscore.
            Repopulates the tree.
        """
        logger.debug("setShowSpecialAttributes: {}".format(show_special_attributes))
        self._show_special_attributes = show_special_attributes
        self.invalidateFilter() class TreeModel(QtCore.QAbstractItemModel):
    """ Model that provides an interface to an objectree that is build of TreeItems. 
    """
    def __init__(self, obj, 
                 obj_name = '',
                 attr_cols = None, 
                 parent = None):
        """ Constructor
        
            :param obj: any Python object or variable
            :param obj_name: name of the object as it will appear in the root node
                             If empty, no root node will be drawn. 
            :param attr_cols: list of AttributeColumn definitions
            :param parent: the parent widget
        """
        super(TreeModel, self).__init__(parent)
        self._attr_cols = attr_cols

        self.regular_font = QtGui.QFont()  # Font for members (non-functions)
        self.special_attribute_font = QtGui.QFont()  # Font for __special_attributes__
        self.special_attribute_font.setItalic(True)
        
        self.regular_color = QtGui.QBrush(QtGui.QColor('black'))    
        #self.callable_color = QtGui.QBrush(QtGui.QColor('brown'))  # for functions, methods, etc.
        self.callable_color = QtGui.QBrush(QtGui.QColor('mediumblue'))  # for functions, methods, etc.

        # The following members will be initialized by populateTree
        # The rootItem is always invisible. If the obj_name is the empty string, the inspectedItem 
        # will be the rootItem (and therefore be invisible). If the obj_name is given, an 
        # invisible root item will be added and the inspectedItem will be its only child. 
        # In that case the inspected item will be visible. 
        self._inspected_node_is_visible = None 
        self._inspected_item = None
        self._root_item = None
        self.populateTree(obj, obj_name)

    
    @property
    def inspectedNodeIsVisible(self):
        """ Returns True if the inspected node is visible. 
            In that case an invisible root node has been added.
        """
        return self._inspected_node_is_visible
    
    
    @property
    def rootItem(self):
        """ The root TreeItem.
        """
        return self._root_item
    
    
    @property
    def inspectedItem(self): # TODO: needed?
        """ The TreeItem that contains the item under inspection.
        """
        return self._inspected_item
    

    def rootIndex(self): # TODO: needed?
        """ The index that returns the root element (same as an invalid index).
        """
        return QtCore.QModelIndex()

    
    def inspectedIndex(self):
        """ The model index that point to the inspectedItem
        """
        if self.inspectedNodeIsVisible:
            return self.createIndex(0, 0, self._inspected_item)
        else:
            return self.rootIndex()
        

    def columnCount(self, _parent=None):
        """ Returns the number of columns in the tree """
        return len(self._attr_cols)


    def data(self, index, role):
        """ Returns the tree item at the given index and role
        """
        if not index.isValid():
            return None

        col = index.column()
        tree_item = index.internalPointer()
        obj = tree_item.obj

        if role == Qt.DisplayRole:
            try:
                attr = self._attr_cols[col].data_fn(tree_item)
                # Replace carriage returns and line feeds with unicode glyphs 
                # so that all table rows fit on one line. 
                #return attr.replace('\n', unichr(0x240A)).replace('\r', unichr(0x240D))
                return (attr.replace('\r\n', unichr(0x21B5))
                            .replace('\n', unichr(0x21B5))
                            .replace('\r', unichr(0x21B5)))
            except Exception as ex:
                #logger.exception(ex)
                return "**ERROR**: {}".format(ex) 
            
        elif role == Qt.TextAlignmentRole:
            return self._attr_cols[col].alignment
            
        elif role == Qt.ForegroundRole:
            if tree_item.is_callable:
                return self.callable_color
            else:
                return self.regular_color
            
        elif role == Qt.FontRole:
            if tree_item.is_attribute:
                return self.special_attribute_font
            else:
                return self.regular_font
        else:
            return None


    def flags(self, index):
        if not index.isValid():
            return Qt.NoItemFlags

        return Qt.ItemIsEnabled | Qt.ItemIsSelectable


    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._attr_cols[section].name
        else:
            return None

    def treeItem(self, index):
        if not index.isValid():
            return self.rootItem
        else:
            return index.internalPointer() 
            

    def index(self, row, column, parent=None):
        
        if parent is None:
            logger.debug("parent is None")
            parent = QtCore.QModelIndex()

        parentItem = self.treeItem(parent)
            
        if not self.hasIndex(row, column, parent):
            logger.debug("hasIndex is False: ({}, {}) {!r}".format(row, column, parentItem))
            #logger.warn("Parent index model: {!r} != {!r}".format(parent.model(), self))

            return QtCore.QModelIndex()

        childItem = parentItem.child(row)
        #logger.debug("  {}".format(childItem.obj_path))
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            logger.warn("no childItem")
            return QtCore.QModelIndex()


    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        child_item = index.internalPointer()
        parent_item = child_item.parent()

        if parent_item is None or parent_item == self.rootItem:
            return QtCore.QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)
    

    def rowCount(self, parent=None):
        parent = QtCore.QModelIndex() if parent is None else parent
        
        if parent.column() > 0:
            # This is taken from the PyQt simpletreemodel example.
            return 0
        else:
            return self.treeItem(parent).child_count()


    def hasChildren(self, parent=None):
        parent = QtCore.QModelIndex() if parent is None else parent
        if parent.column() > 0:
            return 0
        else:
            return self.treeItem(parent).has_children
    

    def canFetchMore(self, parent=None):
        parent = QtCore.QModelIndex() if parent is None else parent
        if parent.column() > 0:
            return 0
        else:
            result = not self.treeItem(parent).children_fetched 
            # logger.debug("canFetchMore: {} = {}".format(parent, result))
            return result  


    def fetchMore(self, parent=None):
        """ Fetches the children given the model index of a parent node.
            Adds the children to the parent.
        """
        parent = QtCore.QModelIndex() if parent is None else parent
        if parent.column() > 0:
            return
        
        parent_item = self.treeItem(parent)
        if parent_item.children_fetched:
            return
                
        tree_items = self._fetchObjectChildren(parent_item.obj, parent_item.obj_path)
        
        self.beginInsertRows(parent, 0, len(tree_items) - 1)
        for tree_item in tree_items:
            parent_item.append_child(tree_item)
            
        parent_item.children_fetched = True                
        self.endInsertRows()
        

    def _fetchObjectChildren(self, obj, obj_path):
        """ Fetches the children of a Python object. 
            Returns: list of TreeItems
        """
        obj_children = []
        path_strings = []
        
        if isinstance(obj, (list, tuple)):
            obj_children = sorted(enumerate(obj))
            path_strings = ['{}[{}]'.format(obj_path, item[0]) if obj_path else item[0] 
                            for item in obj_children]
        elif isinstance(obj, (set, frozenset)):
            obj_children = [('pop()', elem) for elem in sorted(obj)]
            path_strings = ['{0}.pop()'.format(obj_path, item[0]) if obj_path else item[0] 
                            for item in obj_children]
        elif hasattr(obj, 'items'): # dictionaries and the likes. 
            try: 
                obj_children = list(obj.items())
            except Exception as ex:
                # Can happen if the items method expects an argument, for instance the  
                # types.DictType.items method expects a dictionary.
                logger.warn("No items expanded. Objects items() call failed: {}".format(ex))
                obj_children = []

            # Sort keys, except when the object is an OrderedDict.
            if not isinstance(obj, OrderedDict):
                try:
                    obj_children = sorted(obj.items())
                except Exception as ex:
                    logger.debug("Unable to sort dictionary keys: {}".format(ex))
                    
            path_strings = ['{}[{!r}]'.format(obj_path, item[0]) if obj_path else item[0] 
                            for item in obj_children]
        
        assert len(obj_children) == len(path_strings), "sanity check"    
        is_attr_list = [False] * len(obj_children)
        
        # Object attributes
        for attr_name, attr_value in sorted(inspect.getmembers(obj)):
            obj_children.append( (attr_name, attr_value) )
            path_strings.append('{}.{}'.format(obj_path, attr_name) if obj_path else attr_name)
            is_attr_list.append(True)

        assert len(obj_children) == len(path_strings), "sanity check"
        tree_items = []
        for item, path_str, is_attr in zip(obj_children, path_strings, is_attr_list):
            name, child_obj = item
            tree_items.append(TreeItem(child_obj, name, path_str, is_attr))
            
        return tree_items

   
    def populateTree(self, obj, obj_name='', inspected_node_is_visible=None):
        """ Fills the tree using a python object. Sets the rootItem.
        """
        logger.debug("populateTree with object id = 0x{:x}".format(id(obj)))
        
        if inspected_node_is_visible is None:
            inspected_node_is_visible = (obj_name != '')
        self._inspected_node_is_visible = inspected_node_is_visible
        
        if self._inspected_node_is_visible:
            self._root_item = TreeItem(None, '<invisible_root>', '<invisible_root>', None) 
            self._root_item.children_fetched = True
            self._inspected_item = TreeItem(obj, obj_name, obj_name, is_attribute = None)
            self._root_item.append_child(self._inspected_item)
        else:
            # The root itself will be invisible
            self._root_item = TreeItem(obj, obj_name, obj_name, is_attribute = None)
            self._inspected_item = self._root_item
            
            # Fetch all items of the root so we can select the first row in the constructor.
            root_index = self.index(0, 0)
            self.fetchMore(root_index) 
            
                
                
    def _auxRefreshTree(self, tree_index):
        """ Auxiliary function for refreshTree that recursively refreshes the tree nodes.
            
            If the underlying Python object has been changed, we don't want to delete the old
            tree model and create a new one from scratch because this loses all information about
            which nodes are fetched and expanded. Instead the old tree model is updated. Using the
            difflib from the standard library it is determined for a parent node which child nodes
            should be added or removed. This is done based on the node names only, not on the node 
            contents (the underlying Python objects). Testing the underlying nodes for equality
            is potentially slow. It is faster to let the refreshNode function emit the dataChanged
            signal for all cells.
        """
        tree_item = self.treeItem(tree_index)
        logger.debug("_auxRefreshTree({}): {}{}".format(tree_index, tree_item.obj_path, 
                                           "*" if tree_item.children_fetched else ""))
        
        if tree_item.children_fetched:
            
            old_items = tree_item.child_items
            new_items = self._fetchObjectChildren(tree_item.obj, tree_item.obj_path)
            
            old_item_names = [(item.obj_name, item.is_attribute) for item in old_items]
            new_item_names = [(item.obj_name, item.is_attribute) for item in new_items]
            seqMatcher = SequenceMatcher(isjunk=None, a=old_item_names, b=new_item_names, 
                                         autojunk=False)
            opcodes = seqMatcher.get_opcodes()
            
            logger.debug("(reversed) opcodes: {}".format(list(reversed(opcodes))))
            
            for tag, i1, i2, j1, j2 in reversed(opcodes):
                
                if 1 or tag != 'equal':
                    logger.debug("  {:7s}, a[{}:{}] ({}), b[{}:{}] ({})"
                                 .format(tag, i1, i2, old_item_names[i1:i2], j1, j2, new_item_names[j1:j2]))
                
                if tag == 'equal':
                    # Only when node names are equal is _auxRefreshTree called recursively.
                    assert i2-i1 == j2-j1, "equal sanity check failed {} != {}".format(i2-i1, j2-j1)
                    for old_row, new_row in zip(range(i1, i2), range(j1, j2)):
                        old_items[old_row].obj = new_items[new_row].obj
                        child_index = self.index(old_row, 0, parent=tree_index)
                        self._auxRefreshTree(child_index) 

                elif tag == 'replace':
                    # Explicitly remove the old item and insert the new. The old item may have
                    # child nodes which indices must be removed by Qt, otherwise it crashes.
                    assert i2-i1 == j2-j1, "replace sanity check failed {} != {}".format(i2-i1, j2-j1)
                    
                    first = i1          # row number of first that will be removed
                    last  = i1 + i2 - 1 # row number of last element after insertion
                    logger.debug("     calling beginRemoveRows({}, {}, {})".format(tree_index, first, last)) 
                    self.beginRemoveRows(tree_index, first, last)
                    del tree_item.child_items[i1:i2] 
                    self.endRemoveRows()                    

                    first = i1               # row number of first element after insertion 
                    last  = i1 + j2 - j1 - 1 # row number of last element after insertion
                    logger.debug("     calling beginInsertRows({}, {}, {})".format(tree_index, first, last)) 
                    self.beginInsertRows(tree_index, first, last)
                    tree_item.insert_children(i1, new_items[j1:j2])
                    self.endInsertRows()
                    
                elif tag == 'delete':
                    assert j1 == j2, "delete sanity check failed. {} != {}".format(j1, j2)
                    first = i1          # row number of first that will be removed
                    last  = i1 + i2 - 1 # row number of last element after insertion
                    logger.debug("     calling beginRemoveRows({}, {}, {})".format(tree_index, first, last)) 
                    self.beginRemoveRows(tree_index, first, last)
                    del tree_item.child_items[i1:i2] 
                    self.endRemoveRows()
                                            
                elif tag == 'insert':
                    assert i1 == i2, "insert sanity check failed. {} != {}".format(i1, i2)
                    first = i1               # row number of first element after insertion 
                    last  = i1 + j2 - j1 - 1 # row number of last element after insertion
                    logger.debug("     calling beginInsertRows({}, {}, {})".format(tree_index, first, last)) 
                    self.beginInsertRows(tree_index, first, last)
                    tree_item.insert_children(i1, new_items[j1:j2])
                    self.endInsertRows()

                else:
                    raise ValueError("Invalid tag: {}".format(tag))
            
        
    def refreshTree(self):
        """ Refreshes the tree model from the underlying root object (which may have been changed).
        """
        logger.info("")
        logger.info("refreshTree: {}".format(self.rootItem))
        
        root_item = self.treeItem(self.rootIndex())
        logger.info("  root_item:      {} (idx={})".format(root_item, self.rootIndex()))
        inspected_item = self.treeItem(self.inspectedIndex())
        logger.info("  inspected_item: {} (idx={})".format(inspected_item, self.inspectedIndex()))
        
        assert (root_item is inspected_item) != self.inspectedNodeIsVisible, "sanity check"
        
        self._auxRefreshTree(self.inspectedIndex())
        
        root_obj = self.rootItem.obj
        logger.debug("After _auxRefreshTree, root_obj: {}".format(cut_off_str(root_obj, 80)))
        self.rootItem.pretty_print()
        
        # Emit the dataChanged signal for all cells. This is faster than checking which nodes
        # have changed, which may be slow for some underlying Python objects.
        n_rows = self.rowCount()
        n_cols = self.columnCount()
        top_left = self.index(0, 0)
        bottom_right = self.index(n_rows-1, n_cols-1)
        
        logger.debug("bottom_right: ({}, {})".format(bottom_right.row(), bottom_right.column()))
        self.dataChanged.emit(top_left, bottom_right) class ToggleColumnTreeView(QtWidgets.QTreeView, ToggleColumnMixIn):
    """ A QTreeView where right clicking on the header allows the user to show/hide columns
    """
    def _horizontal_header(self):
        """ Returns the horizontal header (of type QHeaderView).
        
            Override this if the horizontalHeader() function does not exist.
        """
        return self.header()    DEFAULT_ATTR_COLS = (
    ATTR_MODEL_NAME,
    ATTR_MODEL_PATH, 
    ATTR_MODEL_SUMMARY,
    ATTR_MODEL_UNICODE, 
    ATTR_MODEL_STR, 
    ATTR_MODEL_REPR,    
    ATTR_MODEL_LENGTH, 
    ATTR_MODEL_TYPE, 
    ATTR_MODEL_CLASS, 
    ATTR_MODEL_ID, 
    ATTR_MODEL_IS_ATTRIBUTE,     
    ATTR_MODEL_CALLABLE, 
    ATTR_MODEL_IS_ROUTINE,     
    ATTR_MODEL_PRED,    
    ATTR_MODEL_GET_MODULE, 
    ATTR_MODEL_GET_FILE, 
    ATTR_MODEL_GET_SOURCE_FILE) DEFAULT_ATTR_DETAILS = (
    ATTR_MODEL_PATH, # to allow for copy/paste  
    #ATTR_MODEL_SUMMARY, # Too similar to unicode column
    ATTR_MODEL_UNICODE, 
    #ATTR_MODEL_STR, # Too similar to unicode column
    ATTR_MODEL_REPR,
    ATTR_MODEL_PRETTY_PRINT,
    #ATTR_MODEL_DOC_STRING, # not used, too similar to ATTR_MODEL_GET_DOC
    ATTR_MODEL_GET_DOC, 
    ATTR_MODEL_GET_COMMENTS, 
    #ATTR_MODEL_GET_MODULE, # not used, already in table 
    ATTR_MODEL_GET_FILE,         
    #ATTR_MODEL_GET_SOURCE_FILE,  # not used, already in table 
    #ATTR_MODEL_GET_SOURCE_LINES, # not used, ATTR_MODEL_GET_SOURCE is better
    ATTR_MODEL_GET_SOURCE)
import logging, traceback, hashlib, sys
from objbrowser.qtpy import QtCore, QtGui, QtWidgets
from objbrowser.qtpy.QtCore import Slot
from objbrowser.app import get_qapp, get_qsettings, start_qt_event_loop
from objbrowser.version import PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_URL, DEBUGGING
from objbrowser.version import PYTHON_VERSION, QT_API_NAME, QT_API, QTPY_VERSION
from objbrowser.utils import setting_str_to_bool
from objbrowser.treemodel import TreeProxyModel, TreeModel
from objbrowser.toggle_column_mixin import ToggleColumnTreeView
from objbrowser.attribute_model import DEFAULT_ATTR_COLS, DEFAULT_ATTR_DETAILS
                 attribute_columns = DEFAULT_ATTR_COLS,
                 attribute_details = DEFAULT_ATTR_DETAILS,
                 show_callable_attributes = None,  # None uses value from QSettings
                 show_special_attributes = None,  # None uses value from QSettings
                 auto_refresh=None,  # None uses value from QSettings
                 refresh_rate=None,  # None uses value from QSettings
                 reset = False):
        """ Constructor
        
            :param obj: any Python object or variable
            :param name: name of the object as it will appear in the root node
            :param attribute_columns: list of AttributeColumn objects that define which columns
                are present in the table and their defaults
            :param attribute_details: list of AttributeDetails objects that define which attributes
                can be selected in the details pane.
            :param show_callable_attributes: if True rows where the 'is attribute' and 'is callable'
                columns are both True, are displayed. Otherwise they are hidden. 
            :param show_special_attributes: if True rows where the 'is attribute' is True and
                the object name starts and ends with two underscores, are displayed. Otherwise 
                they are hidden.
            :param auto_refresh: If True, the contents refershes itsef every <refresh_rate> seconds.
            :param refresh_rate: number of seconds between automatic refreshes. Default = 2 .
            :param reset: If true the persistent settings, such as column widths, are reset. 
        """
        super(ObjectBrowser, self).__init__()

        self._instance_nr = self._add_instance()
        
        # Model
        self._attr_cols = attribute_columns
        self._attr_details = attribute_details
        
        (self._auto_refresh, self._refresh_rate, show_callable_attributes, show_special_attributes) = \
            self._readModelSettings(reset = reset,
                                    auto_refresh = auto_refresh,
                                    refresh_rate = refresh_rate,
                                    show_callable_attributes= show_callable_attributes,
                                    show_special_attributes = show_special_attributes)

        self._tree_model = TreeModel(obj, name, attr_cols = self._attr_cols)
            
        self._proxy_tree_model = TreeProxyModel(
            show_callable_attributes= show_callable_attributes,
            show_special_attributes = show_special_attributes)
        
        self._proxy_tree_model.setSourceModel(self._tree_model)
        #self._proxy_tree_model.setSortRole(RegistryTableModel.SORT_ROLE)
        self._proxy_tree_model.setDynamicSortFilter(True) 
        #self._proxy_tree_model.setSortCaseSensitivity(Qt.CaseInsensitive)
                
        # Views
        self._setup_actions()
        self._setup_menu()
        self._setup_views()
        self.setWindowTitle("{} - {}".format(PROGRAM_NAME, name))

        self._readViewSettings(reset = reset)

        assert self._refresh_rate > 0, "refresh_rate must be > 0. Got: {}".format(self._refresh_rate)
        self._refresh_timer = QtCore.QTimer(self)
        self._refresh_timer.setInterval(self._refresh_rate * 1000)
        self._refresh_timer.timeout.connect(self.refresh)
        
        # Update views with model
        self.toggle_special_attribute_action.setChecked(show_special_attributes)
        self.toggle_callable_action.setChecked(show_callable_attributes)
        self.toggle_auto_refresh_action.setChecked(self._auto_refresh)
     
        # Select first row so that a hidden root node will not be selected.
        first_row_index = self._proxy_tree_model.firstItemIndex()
        self.obj_tree.setCurrentIndex(first_row_index)
        if self._tree_model.inspectedNodeIsVisible:
            self.obj_tree.expand(first_row_index)
        

    def refresh(self):
        """ Refreshes object brawser contents
        """
        logger.debug("Refreshing")
        self._tree_model.refreshTree()
        
        
    def _add_instance(self):
        """ Adds the browser window to the list of browser references.
            If a None is present in the list it is inserted at that position, otherwise
            it is appended to the list. The index number is returned.
            
            This mechanism is used so that repeatedly creating and closing windows does not
            increase the instance number, which is used in writing the persistent settings.
        """
        try:
            idx = self._browsers.index(None)
        except ValueError:
            self._browsers.append(self)
            idx = len(self._browsers) - 1
        else:
            self._browsers[idx] = self

        return idx


    def _remove_instance(self):
        """ Sets the reference in the browser list to None. 
        """
        idx = self._browsers.index(self)
        self._browsers[idx] = None
        
            
    def _make_show_column_function(self, column_idx):
        """ Creates a function that shows or hides a column."""
        show_column = lambda checked: self.obj_tree.setColumnHidden(column_idx, not checked)
        return show_column            


    def _setup_actions(self):
        """ Creates the main window actions.
        """
        # Show/hide callable objects
        self.toggle_callable_action = \
            QtWidgets.QAction("Show callable attributes", self, checkable=True,

                          shortcut = QtGui.QKeySequence("Alt+C"),