예제 #1
0
class Repeater(QtCore.QThread):
    """ execute f forever, signal on every run """

    fragment = QtCore.pyqtSignal(object)

    #@+others
    #@+node:ekr.20121126095734.12428: *3* __init__
    def __init__(self, f, parent=None):

        super().__init__(parent)
        self.f = f

    #@+node:ekr.20121126095734.12429: *3* run
    def run(self):
        while 1:
            try:
                res = self.f()
            except StopIteration:
                return
            except Exception:
                typ, val, tb = sys.exc_info()
                print('')
                print('threadutil.py: unexpected exception in Repeater.run')
                print('')
                # Like g.es_exception()
                lines = traceback.format_exception(typ, val, tb)
                for line in lines:
                    print(line.rstrip())
                return
            self.fragment.emit(res)
예제 #2
0
class UnitWorker(QtCore.QThread):
    """ Work on one work item at a time, start new one when it's done """

    resultReady = QtCore.pyqtSignal()

    #@+others
    #@+node:ekr.20121126095734.12437: *3* __init__
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.cond = QtCore.QWaitCondition()
        self.mutex = QtCore.QMutex()
        self.input = None

    #@+node:ekr.20121126095734.12438: *3* set_worker
    def set_worker(self, f):
        self.worker = f

    #@+node:ekr.20121126095734.12439: *3* set_output_f
    def set_output_f(self, f):
        self.output_f = f

    #@+node:ekr.20121126095734.12440: *3* set_input
    def set_input(self, inp):
        self.input = inp
        self.cond.wakeAll()

    #@+node:ekr.20121126095734.12441: *3* do_work
    def do_work(self, inp):
        #print("Doing work", self.worker, self.input)
        self.output = self.worker(inp)
        #self.output_f(output)

        self.resultReady.emit()

        def L():
            #print "Call output"
            self.output_f(self.output)

        later(L)

    #@+node:ekr.20121126095734.12442: *3* run
    def run(self):

        m = self.mutex
        while 1:
            m.lock()
            self.cond.wait(m)
            inp = self.input
            self.input = None
            m.unlock()
            if inp is not None:
                self.do_work(inp)
예제 #3
0
class Repeater(QtCore.QThread):
    """ execute f forever, signal on every run """

    fragment = QtCore.pyqtSignal(object)

    #@+others
    #@+node:ekr.20121126095734.12428: *3* __init__
    def __init__(self, f, parent=None):

        QtCore.QThread.__init__(self, parent)
        self.f = f

    #@+node:ekr.20121126095734.12429: *3* run
    def run(self):
        while 1:
            try:
                res = self.f()
            except StopIteration:
                return
            self.fragment.emit(res)
예제 #4
0
class UnitWorker(QtCore.QThread):
    """ Work on one work item at a time, start new one when it's done """

    resultReady = QtCore.pyqtSignal()

    #@+others
    #@+node:ekr.20121126095734.12437: *3* __init__
    def __init__(self):
        super().__init__()
        self.cond = QtCore.QWaitCondition()
        self.mutex = QtCore.QMutex()
        self.input = None

    #@+node:ekr.20121126095734.12438: *3* set_worker
    def set_worker(self, f):
        self.worker = f

    #@+node:ekr.20121126095734.12439: *3* set_output_f
    def set_output_f(self, f):
        self.output_f = f

    #@+node:ekr.20121126095734.12440: *3* set_input
    def set_input(self, inp):
        self.input = inp
        self.cond.wakeAll()

    #@+node:ekr.20121126095734.12441: *3* do_work
    def do_work(self, inp):
        #print("Doing work", self.worker, self.input)
        try:
            self.output = self.worker(inp)
        except Exception:
            typ, val, tb = sys.exc_info()
            print('')
            print('threadutil.py: unexpected exception in UnitWorker.do_work.')
            print('')
            # Like g.es_exception()
            lines = traceback.format_exception(typ, val, tb)
            for line in lines:
                print(line.rstrip())
            self.output = ''
        #self.output_f(output)

        self.resultReady.emit()

        def L():
            #print "Call output"
            self.output_f(self.output)

        later(L)

    #@+node:ekr.20121126095734.12442: *3* run
    def run(self):

        m = self.mutex
        while 1:
            m.lock()
            self.cond.wait(m)
            inp = self.input
            self.input = None
            m.unlock()
            if inp is not None:
                self.do_work(inp)
예제 #5
0
class NestedSplitter(QtWidgets.QSplitter):
    enabled = True
    # allow special behavior to be turned of at import stage
    # useful if other code must run to set up callbacks, that
    # other code can re-enable
    Orientations = QtCore.Qt.Orientations if isQt6 else QtCore.Qt
    other_orientation = {
        Orientations.Vertical: Orientations.Horizontal,
        Orientations.Horizontal: Orientations.Vertical,
    }
    # a regular signal, but you can't use its .connect() directly,
    # use splitterClicked_connect()
    _splitterClickedSignal = QtCore.pyqtSignal(QtWidgets.QSplitter,
                                               QtWidgets.QSplitterHandle,
                                               QtGui.QMouseEvent, bool, bool)

    #@+others
    #@+node:ekr.20110605121601.17967: *3* ns.__init__
    def __init__(self, parent=None, orientation=None, root=None):
        """Ctor for NestedSplitter class."""
        Orientations = QtCore.Qt.Orientations if isQt6 else QtCore.Qt
        if orientation is None:
            orientation = Orientations.Horizontal
        super().__init__(orientation, parent)
        # This creates a NestedSplitterHandle.
        if root is None:
            root = self.top(local=True)
            if root == self:
                root.marked = None  # Tuple: self,index,side-1,widget
                root.providers = []
                root.holders = {}
                root.windows = []
                root._main = self.parent()  # holder of the main splitter
                # list of top level NestedSplitter windows opened from 'Open Window'
                # splitter handle context menu
                root.zoomed = False
            #
            # NestedSplitter is a kind of meta-widget, in that it manages
            # panes across multiple actual splitters, even windows.
            # So to create a signal for a click on splitter handle, we
            # need to propagate the .connect() call across all the
            # actual splitters, current and future
            root._splitterClickedArgs = []  # save for future added splitters
        for args in root._splitterClickedArgs:
            # apply any .connect() calls that occured earlier
            self._splitterClickedSignal.connect(*args)

        self.root = root

    #@+node:ekr.20110605121601.17968: *3* ns.__repr__
    def __repr__(self):
        # parent = self.parent()
        # name = parent and parent.objectName() or '<no parent>'
        name = self.objectName() or '<no name>'
        return f"(NestedSplitter) {name} at {id(self)}"

    __str__ = __repr__

    #@+node:ekr.20110605121601.17969: *3* ns.overrides of QSplitter methods
    #@+node:ekr.20110605121601.17970: *4* ns.createHandle
    def createHandle(self, *args, **kargs):
        return NestedSplitterHandle(self)

    #@+node:tbrown.20110729101912.30820: *4* ns.childEvent
    def childEvent(self, event):
        """If a panel client is closed not by us, there may be zero
        splitter handles left, so add an Action button

        unless it was the last panel in a separate window, in which
        case close the window"""
        QtWidgets.QSplitter.childEvent(self, event)
        if not event.removed():
            return
        local_top = self.top(local=True)
        # if only non-placeholder pane in a top level window deletes
        # itself, delete the window
        if (isinstance(local_top.parent(), NestedSplitterTopLevel)
                and local_top.count() == 1
                and  # one left, could be placeholder
                isinstance(local_top.widget(0),
                           NestedSplitterChoice)  # is placeholder
            ):
            local_top.parent().deleteLater()
            return
        # don't leave a one widget splitter
        if self.count() == 1 and local_top != self:
            self.parent().addWidget(self.widget(0))
            self.deleteLater()
        parent = self.parentWidget()
        if parent:
            layout = parent.layout()  # QLayout, not a NestedSplitter
        else:
            layout = None
        if self.count() == 1 and self.top(local=True) == self:
            if self.max_count() <= 1 or not layout:
                # maintain at least two items
                self.insert(0)
                # shrink the added button
                self.setSizes([0] + self.sizes()[1:])
            else:
                # replace ourselves in out parent's layout with our child
                pos = layout.indexOf(self)
                child = self.widget(0)
                layout.insertWidget(pos, child)
                pos = layout.indexOf(self)
                layout.takeAt(pos)
                self.setParent(None)

    #@+node:ekr.20110605121601.17971: *3* ns.add
    def add(self, side, w=None):
        """wrap a horizontal splitter in a vertical splitter, or
        visa versa"""
        orientation = self.other_orientation[self.orientation()]
        layout = self.parent().layout()
        if isinstance(self.parent(), NestedSplitter):
            # don't add new splitter if not needed, i.e. we're the
            # only child of a previously more populated splitter
            if w is None:
                w = NestedSplitterChoice(self.parent())
            self.parent().insertWidget(self.parent().indexOf(self) + side, w)
            # in this case, where the parent is a one child, no handle splitter,
            # the (prior to this invisible) orientation may be wrong
            # can't reproduce this now, but this guard is harmless
            self.parent().setOrientation(orientation)
        elif layout:
            new = NestedSplitter(None, orientation=orientation, root=self.root)
            # parent set by layout.insertWidget() below
            old = self
            pos = layout.indexOf(old)
            new.addWidget(old)
            if w is None:
                w = NestedSplitterChoice(new)
            new.insertWidget(side, w)
            layout.insertWidget(pos, new)
        else:
            # fail - parent is not NestedSplitter and has no layout
            pass

    #@+node:tbrown.20110621120042.22675: *3* ns.add_adjacent
    def add_adjacent(self, what, widget_id, side='right-of'):
        """add a widget relative to another already present widget"""
        Orientations = QtCore.Qt.Orientations if isQt6 else QtCore.Qt
        horizontal, vertical = Orientations.Horizontal, Orientations.Vertical
        layout = self.top().get_layout()

        def hunter(layout, id_):
            """Recursively look for this widget"""
            for n, i in enumerate(layout['content']):
                if (i == id_ or
                    (isinstance(i, QtWidgets.QWidget) and
                     (i.objectName() == id_ or i.__class__.__name__ == id_))):
                    return layout, n
                if not isinstance(i, QtWidgets.QWidget):
                    # then it must be a layout dict
                    x = hunter(i, id_)
                    if x:
                        return x
            return None

        # find the layout containing widget_id

        l = hunter(layout, widget_id)
        if l is None:
            return False
        # pylint: disable=unpacking-non-sequence
        layout, pos = l
        orient = layout['orientation']
        if (orient == horizontal and side in ('right-of', 'left-of')
                or orient == vertical and side in ('above', 'below')):
            # easy case, just insert the new thing, what,
            # either side of old, in existng splitter
            if side in ('right-of', 'below'):
                pos += 1
            layout['splitter'].insert(pos, what)
        else:
            # hard case, need to replace old with a new splitter
            if side in ('right-of', 'left-of'):
                ns = NestedSplitter(orientation=horizontal, root=self.root)
            else:
                ns = NestedSplitter(orientation=vertical, root=self.root)
            old = layout['content'][pos]
            if not isinstance(old, QtWidgets.QWidget):  # see get_layout()
                old = layout['splitter']
            # put new thing, what, in new splitter, no impact on anything else
            ns.insert(0, what)
            # then swap the new splitter with the old content
            layout['splitter'].replace_widget_at_index(pos, ns)
            # now put the old content in the new splitter,
            # doing this sooner would mess up the index (pos)
            ns.insert(0 if side in ('right-of', 'below') else 1, old)
        return True

    #@+node:ekr.20110605121601.17972: *3* ns.choice_menu
    def choice_menu(self, button, pos):
        """build menu on Action button"""
        menu = QtWidgets.QMenu()
        QAction = QtGui.QAction if isQt6 else QtWidgets.QAction
        index = self.indexOf(button)
        if (self.root.marked
                and not self.invalid_swap(button, self.root.marked[3])
                and self.top().max_count() > 2):
            act = QAction("Move marked here", self)
            act.triggered.connect(lambda checked: self.replace_widget(
                button, self.root.marked[3]))
            menu.addAction(act)
        for provider in self.root.providers:
            if hasattr(provider, 'ns_provides'):
                for title, id_ in provider.ns_provides():

                    def cb(checked, id_=id_):
                        self.place_provided(id_, index)

                    act = QAction(title, self)
                    act.triggered.connect(cb)
                    menu.addAction(act)
        if menu.isEmpty():
            act = QAction("Nothing marked, and no options", self)
            menu.addAction(act)

        point = button.position().toPoint() if isQt6 else button.pos(
        )  # Qt6 documentation is wrong.
        global_point = button.mapToGlobal(point)
        menu.exec_(global_point)

    #@+node:tbrown.20120418121002.25712: *3* ns.closing
    def closing(self, window):
        """forget a top-level additional layout which was closed"""
        self.windows.remove(window)

    #@+node:tbrown.20110628083641.11723: *3* ns.place_provided
    def place_provided(self, id_, index):
        """replace Action button with provided widget"""
        provided = self.get_provided(id_)
        if provided is None:
            return
        self.replace_widget_at_index(index, provided)
        self.top().prune_empty()
        # user can set up one widget pane plus one Action pane, then move the
        # widget into the action pane, level 1 pane and no handles
        if self.top().max_count() < 2:
            print('Adding Action widget to maintain at least one handle')
            self.top().insert(0, NestedSplitterChoice(self.top()))

    #@+node:tbrown.20110628083641.11729: *3* ns.context_cb
    def context_cb(self, id_, index):
        """find a provider to provide a context menu service, and do it"""
        for provider in self.root.providers:
            if hasattr(provider, 'ns_do_context'):
                provided = provider.ns_do_context(id_, self, index)
                if provided:
                    break

    #@+node:ekr.20110605121601.17973: *3* ns.contains
    def contains(self, widget):
        """check if widget is a descendent of self"""
        for i in range(self.count()):
            if widget == self.widget(i):
                return True
            if isinstance(self.widget(i), NestedSplitter):
                if self.widget(i).contains(widget):
                    return True
        return False

    #@+node:tbrown.20120418121002.25439: *3* ns.find_child
    def find_child(self, child_class, child_name=None):
        """Like QObject.findChild, except search self.top()
        *AND* each window in self.root.windows
        """
        child = self.top().findChild(child_class, child_name)
        if not child:
            for window in self.root.windows:
                child = window.findChild(child_class, child_name)
                if child:
                    break
        return child

    #@+node:ekr.20110605121601.17974: *3* ns.handle_context
    def handle_context(self, index):
        """for a handle, return (widget, neighbour, count)

        This is the handle's context in the NestedSplitter, not the
        handle's context menu.

        widget
          the pair of widgets either side of the handle
        neighbour
          the pair of NestedSplitters either side of the handle, or None
          if the neighbours are not NestedSplitters, i.e.
          [ns0, ns1] or [None, ns1] or [ns0, None] or [None, None]
        count
          the pair of nested counts of widgets / spliters around the handle
        """
        widget = [self.widget(index - 1), self.widget(index)]
        neighbour = [(i if isinstance(i, NestedSplitter) else None)
                     for i in widget]
        count = []
        for i in 0, 1:
            if neighbour[i]:
                l = [ii.count() for ii in neighbour[i].self_and_descendants()]
                n = sum(l) - len(l) + 1  # count leaves, not splitters
                count.append(n)
            else:
                count.append(1)
        return widget, neighbour, count

    #@+node:tbrown.20110621120042.22920: *3* ns.equalize_sizes
    def equalize_sizes(self, recurse=False):
        """make all pane sizes equal"""
        if not self.count():
            return
        for i in range(self.count()):
            self.widget(i).setHidden(False)
        size = sum(self.sizes()) / self.count()
        self.setSizes([size] * self.count())
        if recurse:
            for i in range(self.count()):
                if isinstance(self.widget(i), NestedSplitter):
                    self.widget(i).equalize_sizes(recurse=True)

    #@+node:ekr.20110605121601.17975: *3* ns.insert (NestedSplitter)
    def insert(self, index, w=None):
        """insert a pane with a widget or, when w==None, Action button"""
        if w is None:  # do NOT use 'not w', fails in PyQt 4.8
            w = NestedSplitterChoice(self)
            # A QWidget, with self as parent.
            # This creates the menu.
        self.insertWidget(index, w)
        self.equalize_sizes()
        return w

    #@+node:ekr.20110605121601.17976: *3* ns.invalid_swap
    def invalid_swap(self, w0, w1):
        """check for swap violating hierachy"""
        return (w0 == w1 or isinstance(w0, NestedSplitter) and w0.contains(w1)
                or isinstance(w1, NestedSplitter) and w1.contains(w0))

    #@+node:ekr.20110605121601.17977: *3* ns.mark
    def mark(self, index, side):
        """mark a widget for later swapping"""
        self.root.marked = (self, index, side - 1,
                            self.widget(index + side - 1))

    #@+node:ekr.20110605121601.17978: *3* ns.max_count
    def max_count(self):
        """find max widgets in this and child splitters"""
        counts = []
        count = 0
        for i in range(self.count()):
            count += 1
            if isinstance(self.widget(i), NestedSplitter):
                counts.append(self.widget(i).max_count())
        counts.append(count)
        return max(counts)

    #@+node:tbrown.20120418121002.25438: *3* ns.open_window
    def open_window(self, action=None):
        """open a top-level window, a TopLevelFreeLayout instance, to hold a
        free-layout in addition to the one in the outline's main window"""
        ns = NestedSplitter(root=self.root)
        window = NestedSplitterTopLevel(owner=self.root,
                                        window_title=ns.get_title(action))
        hbox = QtWidgets.QHBoxLayout()
        window.setLayout(hbox)
        hbox.setContentsMargins(0, 0, 0, 0)
        window.resize(400, 300)
        hbox.addWidget(ns)
        # NestedSplitters must have two widgets so the handle carrying
        # the all important context menu exists
        ns.addWidget(NestedSplitterChoice(ns))
        button = NestedSplitterChoice(ns)
        ns.addWidget(button)
        if action == '_move_marked_there':
            ns.replace_widget(button, ns.root.marked[3])
        elif action is not None:
            ns.place_provided(action, 1)
        ns.setSizes([0, 1])  # but hide one initially
        self.root.windows.append(window)
        # copy the main main window's stylesheet to new window
        w = self.root  # this is a Qt Widget, class NestedSplitter
        sheets = []
        while w:
            s = w.styleSheet()
            if s:
                sheets.append(str(s))
            w = w.parent()
        sheets.reverse()
        ns.setStyleSheet('\n'.join(sheets))
        window.show()

    #@+node:tbrown.20110627201141.11744: *3* ns.register_provider
    def register_provider(self, provider):
        """Register something which provides some of the ns_* methods.

        NestedSplitter tests for the presence of the following methods on
        the registered things, and calls them when needed if they exist.

        ns_provides()
          should return a list of ('Item name', '__item_id') strings,
          'Item name' is displayed in the Action button menu, and
          '__item_id' is used in ns_provide().
        ns_provide(id_)
          should return the widget to replace the Action button based on
          id_, or None if the called thing is not the provider for this id_
        ns_context()
          should return a list of ('Item name', '__item_id') strings,
          'Item name' is displayed in the splitter handle context-menu, and
          '__item_id' is used in ns_do_context().  May also return a dict,
          in which case each key is used as a sub-menu title, whose menu
          items are the corresponding dict value, a list of tuples as above.
          dicts and tuples may be interspersed in lists.
        ns_do_context()
          should do something based on id_ and return True, or return False
          if the called thing is not the provider for this id_
        ns_provider_id()
          return a string identifying the provider (at class or instance level),
          any providers with the same id will be removed before a new one is
          added
        """
        # drop any providers with the same id
        if hasattr(provider, 'ns_provider_id'):
            id_ = provider.ns_provider_id()
            cull = []
            for i in self.root.providers:
                if (hasattr(i, 'ns_provider_id')
                        and i.ns_provider_id() == id_):
                    cull.append(i)
            for i in cull:
                self.root.providers.remove(i)
        self.root.providers.append(provider)

    #@+node:ekr.20110605121601.17980: *3* ns.remove & helper
    def remove(self, index, side):
        widget = self.widget(index + side - 1)
        # clear marked if it's going to be deleted
        if (self.root.marked
                and (self.root.marked[3] == widget
                     or isinstance(self.root.marked[3], NestedSplitter)
                     and self.root.marked[3].contains(widget))):
            self.root.marked = None
        # send close signal to all children
        if isinstance(widget, NestedSplitter):
            count = widget.count()
            all_ok = True
            for splitter in widget.self_and_descendants():
                for i in range(splitter.count() - 1, -1, -1):
                    all_ok &= (self.close_or_keep(splitter.widget(i))
                               is not False)
            if all_ok or count <= 0:
                widget.setParent(None)
        else:
            self.close_or_keep(widget)

    #@+node:ekr.20110605121601.17981: *4* ns.close_or_keep
    def close_or_keep(self, widget, other_top=None):
        """when called from a closing secondary window, self.top() would
        be the top splitter in the closing window, and we need the client
        to specify the top of the primary window for us, in other_top"""
        if widget is None:
            return True
        for k in self.root.holders:
            if hasattr(widget, k):
                holder = self.root.holders[k]
                if holder == 'TOP':
                    holder = other_top or self.top()
                if hasattr(holder, "addTab"):
                    holder.addTab(widget, getattr(widget, k))
                else:
                    holder.addWidget(widget)
                return True
        if widget.close():
            widget.setParent(None)
            return True
        return False

    #@+node:ekr.20110605121601.17982: *3* ns.replace_widget & replace_widget_at_index
    def replace_widget(self, old, new):
        "Swap the provided widgets in place" ""
        sizes = self.sizes()
        new.setParent(None)
        self.insertWidget(self.indexOf(old), new)
        self.close_or_keep(old)
        new.show()
        self.setSizes(sizes)

    def replace_widget_at_index(self, index, new):
        """Replace the widget at index with w."""
        sizes = self.sizes()
        old = self.widget(index)
        if old != new:
            new.setParent(None)
            self.insertWidget(index, new)
            self.close_or_keep(old)
            new.show()
            self.setSizes(sizes)

    #@+node:ekr.20110605121601.17983: *3* ns.rotate
    def rotate(self, descending=False):
        """Change orientation - current rotates entire hierachy, doing less
        is visually confusing because you end up with nested splitters with
        the same orientation - avoiding that would mean doing rotation by
        inserting out widgets into our ancestors, etc.
        """
        Orientations = QtCore.Qt.Orientations if isQt6 else QtCore.Qt
        for i in self.top().self_and_descendants():
            if i.orientation() == Orientations.Vertical:
                i.setOrientation(Orientations.Horizontal)
            else:
                i.setOrientation(Orientations.Vertical)

    #@+node:vitalije.20170713085342.1: *3* ns.rotateOne
    def rotateOne(self, index):
        """Change orientation - only of splithandle at index."""
        psp = self.parent()
        if self.count() == 2 and isinstance(psp, NestedSplitter):
            i = psp.indexOf(self)
            sizes = psp.sizes()
            [a, b] = self.sizes()
            s = sizes[i]
            s1 = a * s / (a + b)
            s2 = b * s / (a + b)
            sizes[i:i + 1] = [s1, s2]
            prev = self.widget(0)
            next = self.widget(1)
            psp.insertWidget(i, prev)
            psp.insertWidget(i + 1, next)
            psp.setSizes(sizes)
            assert psp.widget(i + 2) is self
            psp.remove(i + 3, 0)
            psp.setSizes(sizes)
        elif self is self.root and self.count() == 2:
            self.rotate()
        elif self.count() == 2:
            self.setOrientation(self.other_orientation[self.orientation()])
        else:
            orientation = self.other_orientation[self.orientation()]
            prev = self.widget(index - 1)
            next = self.widget(index)
            if None in (prev, next): return
            sizes = self.sizes()
            s1, s2 = sizes[index - 1:index + 1]
            sizes[index - 1:index + 1] = [s1 + s2]
            newsp = NestedSplitter(self,
                                   orientation=orientation,
                                   root=self.root)
            newsp.addWidget(prev)
            newsp.addWidget(next)
            self.insertWidget(index - 1, newsp)
            prev.setHidden(False)
            next.setHidden(False)
            newsp.setSizes([s1, s2])
            self.setSizes(sizes)

    #@+node:ekr.20110605121601.17984: *3* ns.self_and_descendants
    def self_and_descendants(self):
        """Yield self and all **NestedSplitter** descendants"""
        for i in range(self.count()):
            if isinstance(self.widget(i), NestedSplitter):
                for w in self.widget(i).self_and_descendants():
                    yield w
        yield self

    #@+node:ekr.20110605121601.17985: *3* ns.split (NestedSplitter)
    def split(self, index, side, w=None, name=None):
        """replace the adjacent widget with a NestedSplitter containing
        the widget and an Action button"""
        sizes = self.sizes()
        old = self.widget(index + side - 1)
        #X old_name = old and old.objectName() or '<no name>'
        #X splitter_name = self.objectName() or '<no name>'
        if w is None:
            w = NestedSplitterChoice(self)
        if isinstance(old, NestedSplitter):
            old.addWidget(w)
            old.equalize_sizes()
            #X index = old.indexOf(w)
            #X return old,index # For viewrendered plugin.
        else:
            orientation = self.other_orientation[self.orientation()]
            new = NestedSplitter(self, orientation=orientation, root=self.root)
            #X if name: new.setObjectName(name)
            self.insertWidget(index + side - 1, new)
            new.addWidget(old)
            new.addWidget(w)
            new.equalize_sizes()
            #X index = new.indexOf(w)
            #X return new,index # For viewrendered plugin.
        self.setSizes(sizes)

    #@+node:ekr.20110605121601.17986: *3* ns.swap
    def swap(self, index):
        """swap widgets either side of a handle"""
        self.insertWidget(index - 1, self.widget(index))

    #@+node:ekr.20110605121601.17987: *3* ns.swap_with_marked
    def swap_with_marked(self, index, side):
        # pylint: disable=unpacking-non-sequence
        osplitter, oidx, oside, ow = self.root.marked
        idx = index + side - 1
        # convert from handle index to widget index
        # 1 already subtracted from oside in mark()
        w = self.widget(idx)
        if self.invalid_swap(w, ow):
            return
        self.insertWidget(idx, ow)
        osplitter.insertWidget(oidx, w)
        self.root.marked = self, self.indexOf(ow), 0, ow
        self.equalize_sizes()
        osplitter.equalize_sizes()

    #@+node:ekr.20110605121601.17988: *3* ns.top
    def top(self, local=False):
        """find top (outer) widget, which is not necessarily root"""
        if local:
            top = self
            while isinstance(top.parent(), NestedSplitter):
                top = top.parent()
        else:
            top = self.root._main.findChild(NestedSplitter)
        return top

    #@+node:ekr.20110605121601.17989: *3* ns.get_layout
    def get_layout(self):
        """
        Return a dict describing the layout.

        Usually you would call ns.top().get_layout()
        """
        ans = {
            'content': [],
            'orientation': self.orientation(),
            'sizes': self.sizes(),
            'splitter': self,
        }
        for i in range(self.count()):
            w = self.widget(i)
            if isinstance(w, NestedSplitter):
                ans['content'].append(w.get_layout())
            else:
                ans['content'].append(w)
        return ans

    #@+node:tbrown.20110628083641.11733: *3* ns.get_saveable_layout
    def get_saveable_layout(self):
        """
        Return the dict for saveable layouts.
        
        The content entry for non-NestedSplitter items is the provider ID
        string for the item, or 'UNKNOWN', and the splitter entry is omitted.
        """
        Orientations = QtCore.Qt.Orientations if isQt6 else QtCore.Qt
        ans = {
            'content': [],
            'orientation':
            1 if self.orientation() == Orientations.Horizontal else 2,
            'sizes': self.sizes(),
        }
        for i in range(self.count()):
            w = self.widget(i)
            if isinstance(w, NestedSplitter):
                ans['content'].append(w.get_saveable_layout())
            else:
                ans['content'].append(getattr(w, '_ns_id', 'UNKNOWN'))
        return ans

    #@+node:ekr.20160416083415.1: *3* ns.get_splitter_by_name
    def get_splitter_by_name(self, name):
        """Return the splitter with the given objectName()."""
        if self.objectName() == name:
            return self
        for i in range(self.count()):
            w = self.widget(i)
            # Recursively test w and its descendants.
            if isinstance(w, NestedSplitter):
                w2 = w.get_splitter_by_name(name)
                if w2:
                    return w2
        return None

    #@+node:tbrown.20110628083641.21154: *3* ns.load_layout
    def load_layout(self, c, layout, level=0):

        trace = 'layouts' in g.app.debug
        if trace:
            g.trace('level', level)
            tag = f"layout: {c.shortFileName()}"
            g.printObj(layout, tag=tag)
        if isQt6:
            Orientations = QtCore.Qt.Orientations
            if layout['orientation'] == 1:
                self.setOrientation(Orientations.Horizontal)
            else:
                self.setOrientation(Orientations.Vertical)
        else:
            self.setOrientation(layout['orientation'])
        found = 0
        if level == 0:
            for i in self.self_and_descendants():
                for n in range(i.count()):
                    i.widget(n)._in_layout = False
        for content_layout in layout['content']:
            if isinstance(content_layout, dict):
                new = NestedSplitter(root=self.root, parent=self)
                new._in_layout = True
                self.insert(found, new)
                found += 1
                new.load_layout(c, content_layout, level + 1)
            else:
                provided = self.get_provided(content_layout)
                if provided:
                    self.insert(found, provided)
                    provided._in_layout = True
                    found += 1
                else:
                    print(f"No provider for {content_layout}")
        self.prune_empty()
        if self.count() != len(layout['sizes']):
            not_in_layout = set()
            for i in self.self_and_descendants():
                for n in range(i.count()):
                    c = i.widget(n)
                    if not (hasattr(c, '_in_layout') and c._in_layout):
                        not_in_layout.add(c)
            for i in not_in_layout:
                self.close_or_keep(i)
            self.prune_empty()
        if self.count() == len(layout['sizes']):
            self.setSizes(layout['sizes'])
        else:
            print(f"Wrong pane count at level {level:d}, "
                  f"count:{self.count():d}, "
                  f"sizes:{len(layout['sizes']):d}")
            self.equalize_sizes()

    #@+node:tbrown.20110628083641.21156: *3* ns.prune_empty
    def prune_empty(self):
        for i in range(self.count() - 1, -1, -1):
            w = self.widget(i)
            if isinstance(w, NestedSplitter):
                if w.max_count() == 0:
                    w.setParent(None)
                    # w.deleteLater()

    #@+node:tbrown.20110628083641.21155: *3* ns.get_provided
    def find_by_id(self, id_):
        for s in self.self_and_descendants():
            for i in range(s.count()):
                if getattr(s.widget(i), '_ns_id', None) == id_:
                    return s.widget(i)
        return None

    def get_provided(self, id_):
        """IMPORTANT: nested_splitter should set the _ns_id attribute *only*
        if the provider doesn't do it itself.  That allows the provider to
        encode state information in the id.

        Also IMPORTANT: nested_splitter should call all providers for each id_, not
        just providers which previously advertised the id_.  E.g. a provider which
        advertises leo_bookmarks_show may also be the correct provider for
        leo_bookmarks_show:4532.234 - let the providers decide in ns_provide().
        """
        for provider in self.root.providers:
            if hasattr(provider, 'ns_provide'):
                provided = provider.ns_provide(id_)
                if provided:
                    if provided == 'USE_EXISTING':
                        # provider claiming responsibility, and saying
                        # we already have it, i.e. it's a singleton
                        w = self.top().find_by_id(id_)
                        if w:
                            if not hasattr(w, '_ns_id'):
                                # IMPORTANT: see docstring
                                w._ns_id = id_
                            return w
                    else:
                        if not hasattr(provided, '_ns_id'):
                            # IMPORTANT: see docstring
                            provided._ns_id = id_
                        return provided
        return None

    #@+node:ekr.20200917063155.1: *3* ns.get_title
    def get_title(self, id_):
        """Like get_provided(), but just gets a title for a window
        """
        if id_ is None:
            return "Leo widget window"
        for provider in self.root.providers:
            if hasattr(provider, 'ns_title'):
                provided = provider.ns_title(id_)
                if provided:
                    return provided
        return "Leo unnamed window"

    #@+node:tbrown.20140522153032.32656: *3* ns.zoom_toggle
    def zoom_toggle(self, local=False):
        """zoom_toggle - (Un)zoom current pane to be only expanded pane

        :param bool local: just zoom pane within its own splitter
        """
        if self.root.zoomed:
            for ns in self.top().self_and_descendants():
                if hasattr(ns, '_unzoom'):
                    # this splitter could have been added since
                    ns.setSizes(ns._unzoom)
        else:
            focused = Qt.QApplication.focusWidget()
            parents = []
            parent = focused
            while parent:
                parents.append(parent)
                parent = parent.parent()
            if not focused:
                g.es("Not zoomed, and no focus")
            for ns in (self if local else self.top()).self_and_descendants():
                # FIXME - shouldn't be doing this across windows
                ns._unzoom = ns.sizes()
                for i in range(ns.count()):
                    w = ns.widget(i)
                    if w in parents:
                        sizes = [0] * len(ns._unzoom)
                        sizes[i] = sum(ns._unzoom)
                        ns.setSizes(sizes)
                        break
        self.root.zoomed = not self.root.zoomed

    #@+node:tbnorth.20160510092439.1: *3* ns._splitter_clicked
    def _splitter_clicked(self, handle, event, release, double):
        """_splitter_clicked - coordinate propagation of signals
        for clicks on handles.  Turned out not to need any particular
        coordination, handles could call self._splitterClickedSignal.emit
        directly, but design wise this is a useful control point.

        :param QSplitterHandle handle: handle that was clicked
        :param QMouseEvent event: click event
        :param bool release: was it a release event
        :param bool double: was it a double click event
        """
        self._splitterClickedSignal.emit(self, handle, event, release, double)

    #@+node:tbnorth.20160510123445.1: *3* splitterClicked_connect
    def splitterClicked_connect(self, *args):
        """Apply .connect() args to all actual splitters,
        and store for application to future splitters.
        """
        self.root._splitterClickedArgs.append(args)
        for splitter in self.top().self_and_descendants():
            splitter._splitterClickedSignal.connect(*args)
예제 #6
0
class QNCalendarWidget(QtWidgets.QCalendarWidget):
    def __init__(self, n=3, columns=3, year=None, month=None):
        """set up

        :Parameters:
        - `self`: the widget
        - `n`: number of months to display
        - `columns`: months to display before start a new row
        - `year`: year of first calendar
        - `month`: month of first calendar
        """

        super().__init__()

        self.build(n, columns, year=year, month=month)

    def build(self, n=3, columns=3, year=None, month=None):

        self.calendars = []

        if year is None:
            year = datetime.date.today().year
        if month is None:
            month = datetime.date.today().month

        layout = QtWidgets.QGridLayout()
        while self.layout().count():
            self.layout().removeItem(self.layout().itemAt(0))
        self.layout().addLayout(layout)
        size = self.minimumSizeHint()
        x, y = size.width(), size.height()
        x *= min(n, columns)
        y *= 1 + ((n - 1) // columns)
        self.setMinimumSize(QtCore.QSize(x, y))

        for i in range(n):
            calendar = QtWidgets.QCalendarWidget()
            calendar.i = i
            calendar.setCurrentPage(year, month)
            month += 1
            if month == 13:
                year += 1
                month = 1
            calendar.currentPageChanged.connect(
                lambda year, month, cal=calendar: self.currentPageChanged(
                    year, month, cal))
            calendar.clicked.connect(self.return_result)
            calendar.activated.connect(self.return_result)
            self.calendars.append(calendar)
            layout.addWidget(calendar, i // columns, i % columns)

    def currentPageChanged(self, year, month, cal):
        """currentPageChanged - Handle change of view

        :Parameters:
        - `self`: self
        - `year`: new year
        - `month`: new month
        - `cal`: which calendar
        """

        for i in range(cal.i):
            month -= 1
            if month == 0:
                year -= 1
                month = 12
        for calendar in self.calendars:
            calendar.setCurrentPage(year, month)
            month += 1
            if month == 13:
                year += 1
                month = 1

    activated = QtCore.pyqtSignal(QtCore.QDate)

    def return_result(self, date):
        """return_result - Return result

        :Parameters:
        - `self`: self
        - `cal`: the calendar that was activated
        """

        for i in self.calendars:
            old = i.blockSignals(True)  # stop currentPageChanged firing
            y, m = i.yearShown(), i.monthShown()
            i.setSelectedDate(date)
            i.setCurrentPage(y, m)
            i.blockSignals(old)
        self.activated.emit(date)