from qtsymbols import *
import threading, gobject
from traceback import print_exc
from myutils.config import savehook_new_data, globalconfig
from myutils.wrapper import trypass
import qtawesome, math
from datetime import datetime
from gui.dynalang import LToolButton


def find_best_ticks(max_seconds):
    IDEAL_TICK_COUNT = 5

    nice_intervals = [
        1,
        2,
        3,
        4,
        5,
        6,
        10,
        15,
        20,
        30,
        60,  # 秒
        2 * 60,
        3 * 60,
        4 * 60,
        5 * 60,
        6 * 60,
        10 * 60,
        15 * 60,
        20 * 60,
        30 * 60,  # 分钟
        3600,
        2 * 3600,
        3 * 3600,
        4 * 3600,
        5 * 3600,
        6 * 3600,
        12 * 3600,  # 小时
    ]

    best_option = {"interval": None, "score": float("inf")}

    for interval in nice_intervals:
        num_ticks = max_seconds // interval + 1

        count_penalty = abs(num_ticks - IDEAL_TICK_COUNT) ** 1.5

        axis_end_time = num_ticks * interval
        overshoot_ratio = (
            (axis_end_time - max_seconds) / axis_end_time if axis_end_time > 0 else 0
        )

        total_score = count_penalty * 10 + overshoot_ratio

        if total_score < best_option["score"]:
            best_option = {
                "interval": interval,
                "num_ticks": num_ticks,
                "score": total_score,
            }

    best_interval = best_option["interval"]
    final_num_ticks = best_option["num_ticks"]

    ticks = []
    for i in range(final_num_ticks):
        tick_seconds = i * best_interval
        if tick_seconds > max_seconds:
            continue
        ticks.append(tick_seconds)

    return ticks


class githubheatmaphelper:
    def __init__(self, parent: "chartwidget", timechart: bool):
        self.timechart = timechart
        self.parent = parent
        self.cell_size = 13
        self.cell_spacing = 3
        self.margin_lr = 20
        self.margin_tb = 20

        self.label_x_width = 35
        self.label_y_height = 25

        self.color_bg_light = Qt.GlobalColor.white
        self.color_bg = QColor("#0d1117")  # 主背景色
        self.color_text = QColor("#7d8590")  # 辅助文字颜色
        self.color_text_light = QColor("#767676")
        self.level_colors_dark = [
            QColor("#161b22"),  # Level 0: 极深灰
            QColor("#0e4429"),  # Level 1: 深绿
            QColor("#006d32"),  # Level 2: 中绿
            QColor("#26a641"),  # Level 3: 亮绿
            QColor("#39d353"),  # Level 4: 荧光绿
        ]
        self.level_colors = [
            QColor("#ebedf0"),
            QColor("#9be9a8"),
            QColor("#40c463"),
            QColor("#30a14e"),
            QColor("#216e39"),
        ]
        self.data = {}
        self.end_date = QDate.currentDate()
        days_to_saturday = 6 - (self.end_date.dayOfWeek() % 7)
        self.anchor_date = self.end_date.addDays(days_to_saturday)

    def setdata(self, data: "list[tuple[float, int|dict[str, int]]]"):
        data = sorted(data, key=lambda _: _[0])
        data = {
            QDateTime.fromMSecsSinceEpoch(int(_ * 1000)).date(): __ for _, __ in data
        }
        self.data = data

    def get_level(self, count: "int|dict[str,int]"):
        if isinstance(count, dict):
            count = sum(count.values())
        if self.timechart:
            splits = [60 * 5, 60 * 15, 60 * 60]
        else:
            splits = [10000, 25000, 100000]
        if count <= 0:
            return 0
        if count < splits[0]:
            return 1
        if count < splits[1]:
            return 2
        if count < splits[2]:
            return 3
        return 4

    def paintEvent(self, event):
        painter = QPainter(self.parent)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        painter.fillRect(
            self.parent.rect(),
            self.color_bg if gobject.base.currentisdark else self.color_bg_light,
        )

        step = self.cell_size + self.cell_spacing
        draw_start_x = self.margin_lr + self.label_x_width
        draw_start_y = self.margin_tb + self.label_y_height

        # 使用 QFontMetrics 计算文字宽度
        font = QFont("Segoe UI", 8)
        painter.setFont(font)
        metrics = QFontMetrics(font, self.parent)

        # 1. 绘制左侧星期标签
        painter.setPen(
            self.color_text if gobject.base.currentisdark else self.color_text_light
        )
        for row, text in {1: "Mon", 3: "Wed", 5: "Fri"}.items():
            y_text = draw_start_y + row * step + self.cell_size - 2
            painter.drawText(self.margin_lr, int(y_text), text)

        # 2. 准备绘制方块和月份
        available_width = self.parent.width() - self.margin_lr - draw_start_x
        max_cols = (available_width + self.cell_spacing) // step

        # 核心逻辑：记录上一次绘制月份标签的左边缘 X 坐标
        # 因为是从右向左绘图，所以上一个标签在当前标签的右边
        last_drawn_label_left_x = self.parent.width() + 100

        for col in range(max_cols):
            x = self.parent.width() - self.margin_lr - (col * step) - self.cell_size
            if x < draw_start_x:
                break

            current_week_sat = self.anchor_date.addDays(-(col * 7))  # 本周六
            left_week_sat = self.anchor_date.addDays(-((col + 1) * 7))  # 左边一周的周六

            # 逻辑：如果本周六的月份与左边那一周周六的月份不同，说明本周是该月的起始周
            if current_week_sat.month() != left_week_sat.month():
                month_name = QDate.shortMonthName(current_week_sat.month())
                text_width = metrics.horizontalAdvance(month_name)

                if x + text_width + 5 < last_drawn_label_left_x:
                    painter.setPen(
                        self.color_text
                        if gobject.base.currentisdark
                        else self.color_text_light
                    )
                    painter.drawText(int(x), draw_start_y - 8, month_name)
                    last_drawn_label_left_x = x

            # 3. 绘制方块
            for row in range(7):
                y = draw_start_y + row * step
                days_diff = col * 7 + (6 - row)
                target_date = self.anchor_date.addDays(-days_diff)

                if target_date > self.end_date:
                    continue

                count = self.data.get(target_date, 0)
                color = (
                    self.level_colors_dark
                    if gobject.base.currentisdark
                    else self.level_colors
                )[self.get_level(count)]

                painter.setPen(Qt.PenStyle.NoPen)
                painter.setBrush(QBrush(color))
                painter.drawRoundedRect(
                    QRect(int(x), int(y), self.cell_size, self.cell_size), 2, 2
                )

    def _get_date_at_pos(self, x, y):
        step = self.cell_size + self.cell_spacing
        draw_area_left = self.margin_lr + self.label_x_width
        draw_area_top = self.margin_tb + self.label_y_height

        if draw_area_left <= x <= self.parent.width() - self.margin_lr:
            dist_from_right = self.parent.width() - self.margin_lr - x
            col_from_right = dist_from_right // step
            row = (y - draw_area_top) // step
            if 0 <= row < 7:
                days_diff = col_from_right * 7 + (6 - row)
                return self.anchor_date.addDays(-days_diff)
        return None

    def leaveEvent(self, a0):
        pass

    def mouseMoveEvent(self, event: QMouseEvent):
        pos = event.pos()
        target_date = self._get_date_at_pos(pos.x(), pos.y())
        if target_date and target_date <= self.end_date and self.data.get(target_date):
            data = self.data.get(target_date)
            xt = lambda x: ("" if x == 0 else target_date.toString("yyyy-MM-dd"))
            _ = self.parent.viewpoint(data)
            t = xt(target_date)
            if _:
                t += "\n" + _
            QToolTip.showText(event.globalPos(), t, self.parent)
        else:
            QToolTip.hideText()


class charthelper:
    def __init__(self, parent: "chartwidget") -> None:
        self.parent = parent
        font = QFont()
        font.setPointSize(10)
        fmetrics = QFontMetricsF(font, parent)

        fhall = fmetrics.height()
        self.font = font
        self.data = None
        self.ymargin = int(fhall) + 5
        self.valuewidth = 10
        self.fmetrics = fmetrics
        self.scalelinelen = 5

    def setdata(self, data: "list[tuple[float, int|dict[str, int]]]"):
        data = sorted(data, key=lambda _: _[0])
        self.data = data

    def mouseMoveEvent(self, a0):
        self.parent.update()

    def leaveEvent(self, a0):
        self.parent.update()

    def paintatmouse(
        self, painter: QPainter, xmargin, ymargin, width, height, x_scale, y_scale
    ):
        a0 = QCursor.pos()
        a0 = QPointF(self.parent.mapFromGlobal(a0))
        if a0.x() < xmargin or a0.x() >= xmargin + width:
            return
        if not self.parent.rect().contains(a0.toPoint()):
            return
        idx_f = (a0.x() - xmargin) / x_scale
        idx = round(idx_f)
        idx = max(0, min(idx, len(self.data) - 1))
        xt = lambda x: ("" if x == 0 else str(datetime.fromtimestamp(x)).split(" ")[0])
        _ = self.parent.viewpoint(self.data[idx][1])
        t = xt(self.data[idx][0])
        if _:
            t += "\n" + _
        sz = self.fmetrics.size(0, t)
        a0.setY(a0.y() - sz.height())
        a0.setX(max(0, min(xmargin + width - sz.width(), a0.x())))
        painter.drawText(
            QRectF(a0, sz), Qt.TextFlag.TextWordWrap | Qt.AlignmentFlag.AlignLeft, t
        )
        __x = xmargin + idx * x_scale
        __y = ymargin + height - self.parent.yy(self.data[idx][1]) * y_scale

        pen = painter.pen()
        pen.setWidth(1)
        color = pen.color()
        color.setAlphaF(0.5)
        pen.setColor(color)
        painter.setPen(pen)
        painter.drawLine(QPointF(__x, ymargin), QPointF(__x, ymargin + height))  # Y轴
        painter.drawLine(
            QPointF(xmargin, __y),
            QPointF(xmargin + width, __y),
        )

    def paintEvent(self, _):
        if self.data is None or len(self.data) == 0:
            return
        try:
            if len(self.data) == 1:
                self.data.insert(0, (0, 0))
            painter = QPainter(self.parent)
            painter.setRenderHint(QPainter.RenderHint.Antialiasing)

            pen = QPen(self.parent.palette().color(QPalette.ColorRole.Text))
            pen.setWidth(2)
            painter.setPen(pen)
            painter.setFont(self.font)

            ymargin = self.ymargin

            xmargin = 0
            max_y = int(max(self.parent.yy(y) for _, y in self.data))
            yticks = find_best_ticks(max_y)
            if self.data and self.data[0][1]:
                yticks.insert(0, self.parent.yy(self.data[0][1]))
            y_labels = [self.parent.ytext(y) for y in yticks]

            x_labels = [self.parent.xtext(x) for x, _ in self.data]
            for l in y_labels:
                xmargin = max(xmargin, self.fmetrics.size(0, l).width())

            xmargin = xmargin + self.scalelinelen
            if x_labels:
                w = self.fmetrics.size(0, x_labels[0]).width() / 2
                xmargin = max(w, xmargin)
            width = (
                self.parent.width()
                - xmargin
                - max(
                    self.fmetrics.size(0, x_labels[-1]).width() // 2,
                    self.fmetrics.size(
                        0, self.parent.ytext(self.parent.yy(self.data[-1][1]))
                    ).width()
                    // 2,
                )
            )
            height = self.parent.height() - 2 * ymargin

            # 纵坐标
            rects: "list[QRectF]" = []
            for i, label in enumerate(y_labels):
                y = ymargin + height - height * yticks[i] / (max_y if max_y else 1)
                painter.drawLine(
                    QPointF(xmargin - self.scalelinelen, y), QPointF(xmargin, y)
                )
                fm = self.fmetrics.size(0, label)
                p = QPointF(
                    xmargin - self.scalelinelen - fm.width(),
                    y - fm.height() / 2,
                )
                newrect = QRectF(p, fm)
                if any(_.intersected(newrect) for _ in rects):
                    continue
                else:
                    rects.append(newrect)
                    painter.drawText(newrect, label)  # value

            painter.drawLine(
                QPointF(xmargin, ymargin), QPointF(xmargin, ymargin + height)
            )  # Y轴
            painter.drawLine(
                QPointF(xmargin, ymargin + height),
                QPointF(xmargin + width, ymargin + height),
            )  # X轴

            # 计算数据点在绘图区域中的坐标
            x_scale = width / (len(self.data) - 1)
            y_scale = height / max(max_y, 1)

            points = []
            for i, (x, y) in enumerate(self.data):
                x_coord = xmargin + i * x_scale
                y_coord = ymargin + height - self.parent.yy(y) * y_scale
                points.append((x_coord, y_coord))

            # 绘制折线
            rects: "list[QRectF]" = []
            texth = self.fmetrics.height()
            for i in range(len(points) - 1):
                x1, y1 = points[i]
                x2, y2 = points[i + 1]
                painter.drawLine(QPointF(x1, y1), QPointF(x2, y2))

                if self.data[i + 1][1]:  #!=0
                    text = self.parent.ytext(self.parent.yy(self.data[i + 1][1]))
                    W = self.fmetrics.size(0, text).width()
                    next_ = points[i + 2][1] if ((i + 2) < len(points)) else y2

                    if y1 <= y2 and y2 >= next_:  # 凹
                        yy = y2
                    elif y1 >= y2 and y2 <= next_:  # 凸
                        yy = y2 - texth
                    elif y1 <= y2 and y2 <= next_:
                        yy = y2
                    elif y1 >= y2 and y2 >= next_:
                        yy = y2 - texth
                    if (yy == y2) and (yy + texth > ymargin + height):
                        continue
                    newrect = QRectF(x2 - W // 2, yy, W, texth)
                    if any(_.intersected(newrect) for _ in rects):
                        continue
                    else:
                        rects.append(newrect)
                        painter.drawText(newrect, text)  # value

            lastx2 = -math.inf
            for i, (x, y) in enumerate(points):
                painter.drawLine(
                    QPointF(x, ymargin + height), QPointF(x, ymargin + height + 5)
                )
                fm = self.fmetrics.size(0, x_labels[i])
                thisw = fm.width()
                thisx = x - thisw / 2

                if thisx > lastx2:

                    painter.drawText(
                        QRectF(thisx, ymargin + height + 5, thisw, fm.height()),
                        x_labels[i],
                    )
                    lastx2 = thisx + thisw
            self.paintatmouse(
                painter, xmargin, ymargin, width, height, x_scale, y_scale
            )
        except:
            print_exc()


class chartwidget(QWidget):

    def setdata(self, data):
        self._helper1.setdata(data)
        self._helper2.setdata(data)

    def yy(self, y: "int|float|dict[str, int|float]"):
        return y if isinstance(y, (int, float)) else sum(y.values())

    def viewpoint(self, d: "int|float|dict[str, int|float]"):
        _ = self.ytext(self.yy(d))
        if not _:
            return _
        if isinstance(d, dict):
            __ = []
            for uid, val in sorted(list(d.items()), key=lambda ___: -___[1]):
                __.append(
                    "{} : {}".format(
                        self.ytext(self.yy(val)),
                        savehook_new_data.get(uid, {}).get("title", "???"),
                    )
                )
            if len(d) != 1:
                __.insert(0, _)
            if len(__) > 10:
                __ = __[:10]
                __.append("...")
            _ = "\n".join(__)
        return _

    def __init__(self, timechart):
        super().__init__()
        self.setMouseTracking(True)
        self._helper1 = githubheatmaphelper(self, timechart)
        self._helper2 = charthelper(self)
        self.setMinimumHeight(180)
        self.xtext = lambda x: str(x)
        self.ytext = lambda y: str(y)

    @property
    def usehelper(self):
        return (
            self._helper2
            if globalconfig.get("timecharttype", 0) == 0
            else self._helper1
        )

    def paintEvent(self, event):
        self.usehelper.paintEvent(event)

    def mouseMoveEvent(self, event: QMouseEvent):
        self.usehelper.mouseMoveEvent(event)

    def leaveEvent(self, a0):
        self.usehelper.leaveEvent(a0)


class ScrollArea(QScrollArea):
    scrolled = pyqtSignal(QRect)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.verticalScrollBar().valueChanged.connect(self.handleScroll)

    def handleScroll(self, value):

        viewport_rect = self.viewport().rect()
        horizontal_scrollbar = self.horizontalScrollBar()
        vertical_scrollbar = self.verticalScrollBar()

        x = horizontal_scrollbar.value()
        y = vertical_scrollbar.value()
        width = viewport_rect.width()
        height = viewport_rect.height()

        visible_rect = QRect(x, y, width, height)
        self.scrolled.emit(visible_rect)

    def keyPressEvent(self, e: QKeyEvent):
        if e.key() == Qt.Key.Key_Left or e.key() == Qt.Key.Key_Right:
            return e.ignore()
        super().keyPressEvent(e)


class lazyscrollflow(ScrollArea):
    bgclicked = pyqtSignal()

    def mousePressEvent(self, _2) -> None:
        self.bgclicked.emit()

    def directshow(self):
        QApplication.processEvents()
        self.doshowlazywidget(True, self.internalwid.visibleRegion().boundingRect())

    def __init__(self, keypressed):
        super().__init__()
        self._keypressed = keypressed
        self.setStyleSheet("lazyscrollflow{background: transparent;border:none;}")
        self.widgets = []
        self.fakegeos = []
        self.widgetlogicposmap = []
        self._spacing = 6
        self._margin = self._spacing  # 9
        self.lock = threading.Lock()
        self.internalwid = QWidget(self)
        self.setWidgetResizable(True)
        self.setWidget(self.internalwid)
        self.scrolled.connect(lambda _: self.doshowlazywidget(True, _))

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self.resizeandshow(False)
        return super().resizeEvent(a0)

    @trypass
    def doshowlazywidget(self, procevent, region: QRect):
        needdos = []
        with self.lock:
            for i, geo in enumerate(self.fakegeos):
                if isinstance(self.widgets[i], QWidget):
                    continue

                if not region.intersects(geo):
                    continue

                widfunc = self.widgets[i]
                if not widfunc:
                    continue
                self.widgets[i] = None
                needdos.append((i, widfunc))
        for i, widfunc in needdos:
            try:
                with self.lock:
                    widfunc: QWidget = widfunc()
                    widfunc.setParent(self.internalwid)
                    widfunc.adjustSize()
                    widfunc.setVisible(True)
                    widfunc.setGeometry(self.fakegeos[i])
                    self.widgets[i] = widfunc
                if procevent:
                    QApplication.processEvents()  # 会在最大化时死锁

            except:
                break

    @trypass
    def resizeandshow(self, procevent=True):
        self.fakeresize()
        self.doshowlazywidget(procevent, self.internalwid.visibleRegion())

    def addwidget(self, widfunc):
        self.insertwidget(-1, widfunc)

    @trypass
    def switchidx(self, idx1, idx2):
        with self.lock:
            self.widgets.insert(idx2, self.widgets.pop(idx1))
            self.fakegeos.insert(idx2, self.fakegeos.pop(idx1))
        self.resizeandshow()

    @trypass
    def insertwidget(self, idx, widfunc):
        refresh = True
        with self.lock:
            if idx == -1:
                refresh = False
                idx = len(self.widgets)
            self.widgets.insert(idx, widfunc)
            self.fakegeos.insert(idx, QRect())
        if refresh:
            self.resizeandshow()

    @trypass
    def totop1(self, idx):
        if idx == 0:
            return
        with self.lock:
            self.widgets.insert(0, self.widgets.pop(idx))
            self.fakegeos.insert(0, self.fakegeos.pop(idx))
        self.resizeandshow()

    @trypass
    def removeidx(self, idx):
        with self.lock:
            if idx >= 0 and idx < len(self.widgets):
                w = self.widgets[idx]
                if isinstance(w, QWidget):
                    w.setParent(None)
                    w.deleteLater()
                self.widgets.pop(idx)
                self.fakegeos.pop(idx)
        self.resizeandshow()

    @trypass
    def setfocus(self, idx):
        self.widget(idx).setFocus()

    @trypass
    def widget(self, idx):
        with self.lock:
            if idx > 0 or idx < len(self.widgets):
                return self.widgets[idx]
            else:
                return None

    def setsize(self, size: QSize):
        self._size = size

    def setSpacing(self, s):
        self._spacing = s

    def spacing(self):
        return self._spacing

    def anylyze(self, effective_rect: QRect, space_x, N):
        x = effective_rect.x()
        for _ in range(N):
            next_x = x + self._size.width() + space_x
            if next_x > effective_rect.right():
                return x
            x = next_x
        return x

    def enableidx(self, i):
        with self.lock:
            widfunc = self.widgets[i]
            if not callable(widfunc):
                return
            widfunc: QWidget = widfunc()
            widfunc.setParent(self.internalwid)
            widfunc.adjustSize()
            widfunc.setVisible(True)
            widfunc.setGeometry(self.fakegeos[i])
            self.widgets[i] = widfunc

    def fakeresize(self):
        with self.lock:
            scrollw = (
                self.verticalScrollBar().width()
                if self.verticalScrollBar().isVisible()
                else 0
            )
            effective_rect = QRect(
                self._margin,
                self._margin,
                self.width() - 2 * self._margin - scrollw,
                self.height() - 2 * self._margin,
            )
            x = effective_rect.x()
            y = effective_rect.y()
            space_x = self.spacing()
            space_y = self.spacing()
            dx = (
                effective_rect.right()
                - self.anylyze(effective_rect, space_x, len(self.widgets))
            ) // 2
            self.widgetlogicposmap = []
            currline = []
            for i, wid in enumerate(self.widgets):
                if isinstance(wid, QWidget):
                    resize = True
                else:
                    resize = False
                next_x = x + self._size.width() + space_x
                if next_x > effective_rect.right() and i:
                    x = effective_rect.x()
                    y = y + self._size.height() + space_y
                    next_x = x + self._size.width() + space_x
                    self.widgetlogicposmap.append(currline.copy())
                    currline.clear()
                currline.append(i)
                if resize:
                    wid.setGeometry(QRect(QPoint(x + dx, y), self._size))
                self.fakegeos[i] = QRect(QPoint(x + dx, y), self._size)
                x = next_x
            if currline:
                self.widgetlogicposmap.append(currline.copy())

            new_height = y + self._size.height() + self._margin
        self.internalwid.setFixedHeight(new_height)

    def keyPressEvent(self, e: QKeyEvent):
        self._keypressed(e)

    def calc_last_next_line_offset(self, idx, last, shu=True):
        offset = -1 if last else 1
        if not shu:
            return offset
        for i in range(len(self.widgetlogicposmap)):
            for j in range(len(self.widgetlogicposmap[i])):
                if self.widgetlogicposmap[i][j] == idx:
                    offset = -1 if last else 1
                    i = i + offset
                    if i >= len(self.widgetlogicposmap):
                        i = i % len(self.widgetlogicposmap)
                    need = self.widgetlogicposmap[i]
                    if j >= len(need):
                        i = i + offset
                        if i >= len(self.widgetlogicposmap):
                            i = i % len(self.widgetlogicposmap)
                        need = self.widgetlogicposmap[i]
                    return need[j] - idx


def has_intersection(interval1, interval2):
    start = max(interval1[0], interval2[0])
    end = min(interval1[1], interval2[1])

    if start <= end:
        return True
    else:
        return None


class delayloadvbox(QWidget):

    def setheight(self, h):
        self._h = h
        self.setFixedHeight(len(self.internal_widgets) * self._h)
        for _ in self.internal_widgets:
            if isinstance(_, QWidget):
                _.resize(_.width(), h)

    def __init__(self, h=1):
        super().__init__()
        self._h = h
        self.internal_widgets = []
        self.lock = threading.Lock()
        self.nowvisregion = QRect()

    def resizeEvent(self, e: QResizeEvent):

        if e.oldSize().width() != e.size().width():
            with self.lock:
                for w in self.internal_widgets:
                    if isinstance(w, QWidget):
                        w.resize(self.width(), w.height())
        return super().resizeEvent(e)

    def _dovisinternal(self, procevent, region: QRect):
        if region.isEmpty():
            return
        visrange = (region.y(), region.y() + region.height())
        ylastend = self.y()
        ydiff = self.y()
        needdos = []
        with self.lock:
            for i in range(len(self.internal_widgets)):
                ystart = ylastend
                yend = ystart + self._h
                ylastend = yend
                if isinstance(self.internal_widgets[i], QWidget):
                    self.internal_widgets[i].move(0, ystart - ydiff)
                    continue
                if not has_intersection((ystart, yend), visrange):
                    continue

                widfunc = self.internal_widgets[i]
                if not widfunc:
                    continue
                self.internal_widgets[i] = None
                needdos.append((i, widfunc, ystart - ydiff, self._h))

        for i, widfunc, ystart, h in needdos:
            try:
                with self.lock:
                    widfunc: QWidget = widfunc()
                    widfunc.setParent(self)
                    widfunc.setVisible(True)
                    widfunc.move(0, ystart)
                    widfunc.resize(self.width(), h)
                    self.internal_widgets[i] = widfunc
                if procevent:
                    QApplication.processEvents()
            except:
                break

        self.nowvisregion = region

    @trypass
    def switchidx(self, idx1, idx2):
        with self.lock:
            self.internal_widgets.insert(idx2, self.internal_widgets.pop(idx1))
        self._dovisinternal(False, self.nowvisregion)

    def popw(self, i):
        with self.lock:
            if i >= 0 and i < self.len():
                w = self.internal_widgets[i]
                if isinstance(w, QWidget):
                    w.setParent(None)
                    w.deleteLater()
                self.internal_widgets.pop(i)
            self.setFixedHeight(len(self.internal_widgets) * self._h)
            # setFixedHeight会导致上面的闪烁
        self._dovisinternal(False, self.nowvisregion)

    def torank1(self, i):
        if i == 0:
            return
        with self.lock:
            self.internal_widgets.insert(0, self.internal_widgets.pop(i))
        self._dovisinternal(False, self.nowvisregion)

    def insertw(self, i, wf):
        refresh = True
        with self.lock:
            if i == -1:
                refresh = False
                i = self.len()
            self.internal_widgets.insert(i, wf)
            self.setFixedHeight(len(self.internal_widgets) * self._h)
        if refresh:
            self._dovisinternal(False, self.nowvisregion)

    def w(self, i):
        _ = self.internal_widgets[i]
        if isinstance(_, QWidget):
            return _
        return None

    def len(self):
        return len(self.internal_widgets)


class shownumQPushButton(LToolButton):
    def __init__(self, T):
        super().__init__()
        self.setText(T)
        self.num = 0
        self.setCheckable(True)
        self.toggled.connect(self.setChecked)
        self.setCursor(Qt.CursorShape.PointingHandCursor)
        self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)

    def setChecked(self, checked):
        self.setIcon(
            qtawesome.icon("fa.chevron-down" if checked else "fa.chevron-right")
        )
        super().setChecked(checked)

    def setnum(self, num):
        self.num = num
        self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        if not self.num:
            return
        painter = QPainter(self)
        c = self.palette().color(QPalette.ColorRole.Text)
        c.setAlphaF(0.5)
        painter.setPen(c)
        rect = self.rect()

        textRect = self.fontMetrics().boundingRect(self.text())
        numberRect = rect.adjusted(textRect.width() + 10, 0, -10, 0)
        painter.drawText(
            numberRect,
            Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
            str(self.num),
        )


class shrinkableitem(QWidget):
    def __init__(self, p, shrinker: shownumQPushButton, opened):
        super().__init__(p)
        self.lay = QVBoxLayout(self)
        # self.setSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed)
        self.lay.setContentsMargins(0, 0, 0, 0)
        self.lay.setSpacing(0)
        self.btn = shrinker
        self.items = delayloadvbox()
        self.btn.clicked.connect(self.Revert)
        self.lay.addWidget(self.btn)
        self.lay.addWidget(self.items)
        self.items.setVisible(opened)
        shrinker.setChecked(opened)
        self._ref_p_stackedlist = p
        self._h = 1

    def visheight(self):
        hh = self.btn.height()
        if self.items.isVisible():
            hh += self.items.height()
        return hh

    def _dovisinternal(self, procevent, region: QRect):
        if not self.items.isVisible():
            return

        region.setY(region.y() - self.btn.height())
        # region.setHeight(region.height() - self.btn.height())
        # 按理说应该减的，但不知道其他哪里写错了，下面少几十个像素的高度，就这样吧。
        self.items._dovisinternal(procevent, QRect(region))

    def Revert(self):
        self.items.setVisible(not self.items.isVisible())
        self._ref_p_stackedlist.doshowlazywidget(True)

    def settitle(self, text):
        self.btn.setText(text)

    @trypass
    def switchidx(self, idx1, idx2):
        self.items.switchidx(idx1, idx2)

    def insertw(self, i, wf):
        self.items.insertw(i, wf)

    def torank1(self, i):
        self.items.torank1(i)

    def popw(self, i):
        return self.items.popw(i)

    def w(self, i):
        return self.items.w(i)

    def len(self):
        return self.items.len()

    def button(self):
        return self.btn

    def setheight(self, h):
        self.items.setheight(h)


class stackedlist(ScrollArea):
    bgclicked = pyqtSignal()

    def setheight(self, h):
        for i in range(self.len()):
            self.w(i).setheight(h)
        self._h = h

    def mousePressEvent(self, _2) -> None:
        self.bgclicked.emit()

    def directshow_1(self):
        self.doshowlazywidget(True, self.internal.visibleRegion().boundingRect())

    def directshow(self):
        QApplication.processEvents()
        self.doshowlazywidget(True, self.internal.visibleRegion().boundingRect())

    def resizeEvent(self, e: QResizeEvent):
        try:
            self.doshowlazywidget(False, self.internal.visibleRegion().boundingRect())
        except:
            pass
        return super().resizeEvent(e)

    def __init__(self):
        super().__init__()
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

        self._h = 1
        self.setStyleSheet(
            """QWidget#shit{background-color:transparent;}QScrollArea{background-color:transparent;border:0px}"""
        )
        self.setWidgetResizable(True)
        internal = QWidget()
        internal.setObjectName("shit")
        self.setWidget(internal)
        self.setWidgetResizable(True)
        self.lay = QVBoxLayout(internal)
        self.lay.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.lay.setSpacing(0)
        self.lay.setContentsMargins(0, 0, 0, 0)
        self.widgets = []
        self.fakegeos = []
        self._spacing = 6
        self._margin = self._spacing  # 9
        self.lock = threading.Lock()
        self.internal = internal
        self.scrolled.connect(lambda _: self.doshowlazywidget(True, _))
        self.saveregion = QRect()

    @trypass
    def doshowlazywidget(self, procevent, region: QRect = None):
        if region:
            self.saveregion = QRect(region)
        else:
            region = QRect(self.saveregion)
        for i in range(self.len()):
            self.w(i)._dovisinternal(procevent, QRect(region))
            region.setY(region.y() - self.w(i).visheight())
            region.setHeight(region.height() - self.w(i).visheight())

    def insertw(self, i, w: shrinkableitem):
        w.setheight(self._h)
        self.lay.insertWidget(i, w)

    def popw(self, i) -> shrinkableitem:
        self.lay.takeAt(i).widget().deleteLater()

    def w(self, i) -> shrinkableitem:
        return self.lay.itemAt(i).widget()

    def len(self):
        return self.lay.count()

    def switchidx(self, i1, i2):
        self.lay.insertItem(i2, self.lay.takeAt(i1))


class KeyPressDetector(QLineEdit):
    callback = pyqtSignal(str)

    def __init__(self, *_):
        super().__init__(*_)
        self.setReadOnly(True)
        self.callback.connect(self.setText)
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)

    def keyPressEvent(self, a0: QKeyEvent):
        if a0.modifiers() & Qt.KeyboardModifier.ControlModifier:
            self.callback.emit("Ctrl")
        elif a0.modifiers() & Qt.KeyboardModifier.AltModifier:
            self.callback.emit("Alt")
        elif a0.modifiers() & Qt.KeyboardModifier.ShiftModifier:
            self.callback.emit("Shift")
        elif a0.modifiers() & Qt.KeyboardModifier.MetaModifier:
            pass
        elif a0.key() == Qt.Key.Key_Meta:
            pass
        else:
            self.callback.emit(QKeySequence(a0.key()).toString())

    def mousePressEvent(self, a0: QMouseEvent):
        if a0.button() == Qt.MouseButton.LeftButton:
            self.callback.emit("LButton")
        elif a0.button() == Qt.MouseButton.RightButton:
            self.callback.emit("RButton")
        elif a0.button() == Qt.MouseButton.MidButton:
            self.callback.emit("MButton")
        else:
            pass
