mirrored 7 minutes ago
0
Adam Yanxiao ZhaoAdd AutoGLM-OS agent (#309) * autoglm-os initialize * clean code * chore: use proxy for download setup * feat(autoglm-os): add parameter to toggle images * fix: use temporary directory for files pulled from the vm to prevent potential collision when running multiple instances of the same task in parallel * update * add client_password * update multienv * fix * fix prompt * fix prompt * fix prompt * fix sys prompt * feat: use proxy in file evaluator * fix client_password * fix note_prompt * fix autoglm agent cmd type * fix * revert: fix: use temporary directory for files pulled from the vm to prevent potential collision when running multiple instances of the same task in parallel reverts commit bab5473eea1de0e61b0e1d68b23ce324a5b0ee57 * feat(autoglm): setup tools * fix(autoglm): remove second time of get a11y tree * add osworld server restart * Revert "add osworld server restart" This reverts commit 7bd9d84122e246ce2a26de0e49c25494244c2b3d. * fix _launch_setup * fix autoglm agent tools & xml tree * fix desktop_env * fix bug for tool name capitalization * fix: always use proxy for setup download * add fail after exceeding max turns * fix(autoglm): avoid adding image to message when screenshot is empty * fix maximize_window * fix maximize_window * fix maximize_window * fix import browsertools module bug * fix task proxy config bug * restore setup * refactor desktop env * restore image in provider * restore file.py * refactor desktop_env * quick fix * refactor desktop_env.step * fix our env reset * add max truns constraint * clean run script * clean lib_run_single.py --------- Co-authored-by: hanyullai <hanyullai@outlook.com> Co-authored-by: JingBh <jingbohao@yeah.net>aa05f6c
import json
import os
import subprocess
import sys

import uno
from com.sun.star.beans import PropertyValue


class CalcTools:
    localContext = uno.getComponentContext()
    resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext)
    ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
    desktop = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
    doc = desktop.getCurrentComponent()
    sheet = doc.CurrentController.ActiveSheet
    ret = ""

    @classmethod
    def close_other_window(cls):
        """关闭除当前文档外的所有文档"""
        # 获取所有打开的文档
        components = cls.desktop.getComponents().createEnumeration()
        current_url = cls.doc.getURL()

        while components.hasMoreElements():
            doc = components.nextElement()
            if doc.getURL() != current_url:  # 如果不是当前文档
                doc.close(True)  # True 表示保存更改

    @classmethod
    def maximize_window(cls):
        """
        将窗口设置为工作区最大尺寸
        使用工作区域大小(考虑任务栏等)
        """
        window = cls.doc.getCurrentController().getFrame().getContainerWindow()
        toolkit = window.getToolkit()
        device = toolkit.createScreenCompatibleDevice(0, 0)

        # 获取工作区域(排除任务栏等)
        workarea = toolkit.getWorkArea()

        # 设置窗口位置和大小为工作区域
        window.setPosSize(workarea.X, workarea.Y, workarea.Width, workarea.Height, 15)

    @classmethod
    def print_result(cls):
        print(cls.ret)

    @classmethod
    def save(cls):
        """
        Save the current workbook to its current location

        Returns:
            bool: True if save successful, False otherwise
        """
        try:
            # Just save the document
            cls.doc.store()
            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def _get_column_index(cls, column_name, sheet=None):
        """
        Get the index of a column by its name (A, B, C, ...)

        Args:
            column_name (str): Name of the column

        Returns:
            int: Index of the column
        """
        try:
            return ord(column_name[0]) - ord("A")
        except ValueError:
            return None

    @classmethod
    def _get_last_used_column(cls):
        """
        Get the last used column index

        Args:
            None

        Returns:
            int: Index of the last used column
        """
        cursor = cls.sheet.createCursor()
        cursor.gotoEndOfUsedArea(False)
        return cursor.RangeAddress.EndColumn

    @classmethod
    def _get_last_used_row(cls):
        """
        Get the last used row index

        Args:
            None

        Returns:
            int: Index of the last used row
        """
        cursor = cls.sheet.createCursor()
        cursor.gotoEndOfUsedArea(False)
        return cursor.RangeAddress.EndRow

    @classmethod
    def _column_name_to_index(cls, column_name):
        """
        将列名转换为列索引

        Args:
            column_name (str): 列名,如 'A', 'AB'

        Returns:
            int: 列索引(从0开始)
        """
        column_name = column_name.upper()
        result = 0
        for char in column_name:
            result = result * 26 + (ord(char) - ord("A") + 1)
        return result - 1

    @classmethod
    def get_workbook_info(cls):
        """
        Get workbook information

        Args:
            None

        Returns:
            dict: Workbook information, including file path, file name, sheets and active sheet
        """
        try:
            info = {
                "file_path": cls.doc.getLocation(),
                "file_title": cls.doc.getTitle(),
                "sheets": [],
                "active_sheet": cls.sheet.Name,
            }

            # Get sheets information
            sheets = cls.doc.getSheets()
            info["sheet_count"] = sheets.getCount()

            # Get all sheet names and info
            for i in range(sheets.getCount()):
                sheet = sheets.getByIndex(i)
                cursor = sheet.createCursor()
                cursor.gotoEndOfUsedArea(False)
                end_col = cursor.getRangeAddress().EndColumn
                end_row = cursor.getRangeAddress().EndRow

                sheet_info = {
                    "name": sheet.getName(),
                    "index": i,
                    "visible": sheet.IsVisible,
                    "row_count": end_row + 1,
                    "column_count": end_col + 1,
                }
                info["sheets"].append(sheet_info)

                # Check if this is the active sheet
                if sheet == cls.sheet:
                    info["active_sheet"] = sheet_info

            cls.ret = json.dumps(info, ensure_ascii=False)
            return info

        except Exception as e:
            cls.ret = f"Error: {e}"

    @classmethod
    def env_info(cls, sheet_name=None):
        """
        Get content of the specified or active sheet

        Args:
            sheet_name (str, optional): Name of the sheet to read. If None, uses active sheet

        Returns:
            dict: Sheet information including name, headers and data
        """
        try:
            # Get the target sheet
            if sheet_name is not None:
                sheet = cls.doc.getSheets().getByName(sheet_name)
            else:
                sheet = cls.sheet

            # Create cursor to find used range
            cursor = sheet.createCursor()
            cursor.gotoEndOfUsedArea(False)
            end_col = cursor.getRangeAddress().EndColumn
            end_row = cursor.getRangeAddress().EndRow

            # Generate column headers (A, B, C, ...)
            col_headers = [chr(65 + i) for i in range(end_col + 1)]

            # Get displayed values from cells
            data_array = []
            for row in range(end_row + 1):
                row_data = []
                for col in range(end_col + 1):
                    cell = sheet.getCellByPosition(col, row)
                    row_data.append(cell.getString())
                data_array.append(row_data)

            # Calculate maximum width for each column
            col_widths = [len(header) for header in col_headers]  # Initialize with header lengths
            for row in data_array:
                for i, cell in enumerate(row):
                    col_widths[i] = max(col_widths[i], len(str(cell)))

            # Format the header row
            header_row = "  | " + " | ".join(f"{h:<{w}}" for h, w in zip(col_headers, col_widths)) + " |"
            separator = "--|-" + "-|-".join("-" * w for w in col_widths) + "-|"

            # Format data rows with row numbers
            formatted_rows = []
            for row_idx, row in enumerate(data_array, 1):
                row_str = f"{row_idx:<2}| " + " | ".join(f"{cell:<{w}}" for cell, w in zip(row, col_widths)) + " |"
                formatted_rows.append(row_str)

            # Combine all parts
            formated_data = header_row + "\n" + separator + "\n" + "\n".join(formatted_rows)

            # Get sheet properties
            sheet_info = {
                "name": sheet.getName(),
                "data": formated_data,
                "row_count": end_row + 1,
                "column_count": end_col + 1,
            }

            cls.ret = json.dumps(sheet_info, ensure_ascii=False)
            return sheet_info

        except Exception as e:
            cls.ret = f"Error: {e}"

    @classmethod
    def get_column_data(cls, column_name):
        """
        Get data from the specified column

        Args:
            column_name (str): Name of the column to read

        Returns:
            list: List of values in the specified column
        """
        column_index = cls._get_column_index(column_name)
        if column_index is None:
            return "Column not found"
        last_row = cls._get_last_used_row()
        _range = cls.sheet.getCellRangeByPosition(column_index, 0, column_index, last_row)
        # 获取数据数组并展平
        cls.ret = json.dumps([row[0] for row in _range.getDataArray()], ensure_ascii=False)
        return [row[0] for row in _range.getDataArray()]

    @classmethod
    def switch_active_sheet(cls, sheet_name):
        """
        Switch to the specified sheet and make it active, create if not exist

        Args:
            sheet_name (str): Name of the sheet to switch to or create

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取所有工作表
            sheets = cls.doc.getSheets()

            # 检查工作表是否存在
            if not sheets.hasByName(sheet_name):
                # 创建新工作表
                new_sheet = cls.doc.createInstance("com.sun.star.sheet.Spreadsheet")
                sheets.insertByName(sheet_name, new_sheet)

            # 获取目标工作表
            sheet = sheets.getByName(sheet_name)

            # 切换到目标工作表
            cls.doc.getCurrentController().setActiveSheet(sheet)

            # 更新当前工作表引用
            cls.sheet = sheet
            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def set_column_values(cls, column_name, data, start_index=2):
        """
        Set data to the specified column

        Args:
            column_name (str): Name of the column to write
            data (list): List of values to write to the column
            start_index (int): The index of the first row to write to, default is 2 (skip the first row)

        Returns:
            bool: True if successful, False otherwise
        """
        # 获取列的索引
        column_index = cls._get_column_index(column_name)
        if column_index is None:
            cls.ret = "Column not found"
            return False
        for i, value in enumerate(data):
            cell = cls.sheet.getCellByPosition(column_index, i + start_index - 1)
            if type(value) == float and value.is_integer():
                cell.setNumber(int(value))
            else:
                cell.setString(str(value))
        cls.ret = "Success"
        return True

    @classmethod
    def highlight_range(cls, range_str, color=0xFF0000):
        """
        highlight the specified range with the specified color

        Args:
            range_str (str): Range to highlight, in the format of "A1:B10"
            color (str): Color to highlight with, default is '0xFF0000' (red)

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            _range = cls.sheet.getCellRangeByName(range_str)
            _range.CellBackColor = color
            cls.ret = "Success"
            return True
        except:
            cls.ret = "False"
            return False

    @classmethod
    def transpose_range(cls, source_range, target_cell):
        """
        Transpose the specified range and paste it to the target cell

        Args:
            source_range (str): Range to transpose, in the format of "A1:B10"
            target_cell (str): Target cell to paste the transposed data, in the format of "A1"

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            source = cls.sheet.getCellRangeByName(source_range)
            target = cls.sheet.getCellRangeByName(target_cell)

            data = source.getDataArray()
            # 转置数据
            transposed_data = list(map(list, zip(*data)))

            # 设置转置后的数据
            target_range = cls.sheet.getCellRangeByPosition(
                target.CellAddress.Column,
                target.CellAddress.Row,
                target.CellAddress.Column + len(transposed_data[0]) - 1,
                target.CellAddress.Row + len(transposed_data) - 1,
            )
            target_range.setDataArray(transposed_data)
            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def export_to_csv(cls):
        """
        Export the current document to a CSV file

        Args:
            None

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取当前文档的URL
            doc_url = cls.doc.getURL()
            if not doc_url:
                raise ValueError("Document must be saved first")

            # 构造CSV文件路径
            if doc_url.startswith("file://"):
                base_path = doc_url[7:]  # 移除 'file://' 前缀
            else:
                base_path = doc_url

            # 获取基本路径和文件名
            csv_path = os.path.splitext(base_path)[0] + ".csv"

            # 确保路径是绝对路径
            csv_path = os.path.abspath(csv_path)

            # 转换为 LibreOffice URL 格式
            csv_url = uno.systemPathToFileUrl(csv_path)

            # 设置CSV导出选项
            props = (
                PropertyValue(Name="FilterName", Value="Text - txt - csv (StarCalc)"),
                PropertyValue(
                    Name="FilterOptions", Value="44,0,76,0"
                ),  # 44=comma, 34=quote, 76=UTF-8, 1=first row as header
            )

            # 导出文件
            cls.doc.storeToURL(csv_url, props)
            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def sort_column(cls, column_name, ascending=True, start_index=2):
        """
        Sorts the data in the specified column in ascending or descending order

        Args:
            column_name (str): The name of the column to sort (e.g. 'A') or the title
            ascending (bool): Whether to sort in ascending order (default True)
            start_index (int): The index of the first row to sort, default is 1

        Returns:
            bool: True if successful, False otherwise
        """

        try:
            column_data = cls.get_column_data(column_name)[start_index - 1 :]
            column_data = sorted(column_data, key=lambda x: float(x), reverse=not ascending)
        except:
            cls.ret = "Error: Invalid column name or data type"
            return False

        return cls.set_column_values(column_name, column_data, start_index)

    @classmethod
    def set_validation_list(cls, column_name, values):
        """
        Set a validation list for the specified column

        Args:
            column_name (str): The name of the column to set the validation list for
            values (list): The list of values to use for the validation list

        Returns:
            None
        """
        try:
            column_index = cls._get_column_index(column_name)
            last_row = cls._get_last_used_row()
            cell_range = cls.sheet.getCellRangeByPosition(column_index, 1, column_index, last_row)

            # 获取现有的验证对象
            validation = cell_range.getPropertyValue("Validation")

            # 设置基本验证类型
            validation.Type = uno.Enum("com.sun.star.sheet.ValidationType", "LIST")
            validation.Operator = uno.Enum("com.sun.star.sheet.ConditionOperator", "EQUAL")

            # 设置下拉列表
            validation.ShowList = True
            values_str = ";".join(str(val) for val in values)
            validation.Formula1 = values_str

            # 应用验证设置回单元格范围
            cell_range.setPropertyValue("Validation", validation)

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def hide_row_data(cls, value="N/A"):
        """
        Hide rows that contain the specified value

        Args:
            value (str): The value to hide rows for, default is 'N/A'

        Returns:
            None
        """
        last_row = cls._get_last_used_row()
        last_col = cls._get_last_used_column()

        for row in range(1, last_row + 1):
            has_value = False
            for col in range(last_col + 1):
                cell = cls.sheet.getCellByPosition(col, row)
                if cell.getString() == value:
                    has_value = True
                    break
            row_range = cls.sheet.getRows().getByIndex(row)
            row_range.IsVisible = not has_value

        cls.ret = "Success"
        return True

    @classmethod
    def reorder_columns(cls, column_order):
        """
        Reorder the columns in the sheet according to the specified order

        Args:
            column_order (list): A list of column names in the desired order

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取新的列索引
            new_indices = [cls._get_column_index(col) for col in column_order]

            # 创建新的列顺序
            for new_index, old_index in enumerate(new_indices):
                if new_index != old_index:
                    cls.sheet.Columns.insertByIndex(new_index, 1)
                    source = cls.sheet.Columns[old_index + (old_index > new_index)]
                    target = cls.sheet.Columns[new_index]
                    target.setDataArray(source.getDataArray())
                    cls.sheet.Columns.removeByIndex(old_index + (old_index > new_index), 1)
            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def create_pivot_table(
        cls,
        source_sheet,
        table_name,
        row_fields=None,
        col_fields=None,
        value_fields=None,
        aggregation_function="sum",
        target_cell="A1",
    ):
        """
        Create a pivot table in the active worksheet based on data from the active sheet.
        """
        try:
            source = cls.doc.getSheets().getByName(source_sheet)

            # 获取数据范围
            cursor = source.createCursor()
            cursor.gotoEndOfUsedArea(False)
            end_col = cursor.getRangeAddress().EndColumn
            end_row = cursor.getRangeAddress().EndRow

            # 获取完整的数据范围
            source_range = source.getCellRangeByPosition(0, 0, end_col, end_row)

            # 获取数据透视表集合
            dp_tables = cls.sheet.getDataPilotTables()

            # 创建数据透视表描述符
            dp_descriptor = dp_tables.createDataPilotDescriptor()

            # 设置数据源
            dp_descriptor.setSourceRange(source_range.getRangeAddress())

            # 设置行字段
            if row_fields:
                for field in row_fields:
                    field_index = cls._get_column_index(field)
                    dimension = dp_descriptor.getDataPilotFields().getByIndex(field_index)
                    dimension.Orientation = uno.Enum("com.sun.star.sheet.DataPilotFieldOrientation", "ROW")

            # 设置列字段
            if col_fields:
                for field in col_fields:
                    field_index = cls._get_column_index(field)
                    dimension = dp_descriptor.getDataPilotFields().getByIndex(field_index)
                    dimension.Orientation = uno.Enum("com.sun.star.sheet.DataPilotFieldOrientation", "COLUMN")

            # 设置数据字段
            for field in value_fields:
                field_index = cls._get_column_index(field)
                dimension = dp_descriptor.getDataPilotFields().getByIndex(field_index)
                dimension.Orientation = uno.Enum("com.sun.star.sheet.DataPilotFieldOrientation", "DATA")

                # 设置聚合函数
                function_map = {"Count": "COUNT", "Sum": "SUM", "Average": "AVERAGE", "Min": "MIN", "Max": "MAX"}

                if aggregation_function in function_map:
                    dimension.Function = uno.Enum(
                        "com.sun.star.sheet.GeneralFunction", function_map[aggregation_function]
                    )

            # 在当前工作表中创建数据透视表
            dp_tables.insertNewByName(
                table_name,  # 透视表名称
                cls.sheet.getCellRangeByName(target_cell).CellAddress,  # 目标位置
                dp_descriptor,  # 描述符
            )

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def merge_cells(cls, range_str):
        """
        合并活动工作表中指定范围的单元格

        Args:
            range_str (str): 要合并的单元格范围,格式为'A1:B10'

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 获取当前活动工作表
            sheet = cls.sheet

            # 获取单元格范围
            cell_range = sheet.getCellRangeByName(range_str)

            # 获取单元格范围的属性
            range_props = cell_range.getIsMerged()

            # 如果单元格范围尚未合并,则进行合并
            if not range_props:
                cell_range.merge(True)

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def set_cell_value(cls, cell, value):
        """
        Set a value to a specific cell in the active worksheet.

        Args:
            cell (str): Cell reference (e.g., 'A1')
            value (str): Value to set in the cell

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取单元格对象
            cell_obj = cls.sheet.getCellRangeByName(cell)

            if isinstance(value, str) and value.startswith("="):
                # 设置公式
                cell_obj.Formula = value
                cls.ret = "Success"
                return True

            # 尝试将值转换为数字
            try:
                # 尝试转换为整数
                int_value = int(value)
                cell_obj.Value = int_value
            except ValueError:
                try:
                    # 尝试转换为浮点数
                    float_value = float(value)
                    cell_obj.Value = float_value
                except ValueError:
                    # 如果不是数字,则设置为字符串
                    cell_obj.String = value

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def format_range(cls, range_str, background_color=None, font_color=None, bold=None, alignment=None):
        """
        Apply formatting to the specified range in the active worksheet

        Args:
            range_str (str): Range to format, in the format of 'A1:B10'
            background_color (str, optional): Background color in hex format (e.g., '#0000ff')
            font_color (str, optional): Font color in hex format (e.g., '#ffffff')
            bold (bool, optional): Whether to make the text bold
            italic (bool, optional): Whether to make the text italic
            alignment (str, optional): Text alignment (left, center, right)

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取指定范围
            cell_range = cls.sheet.getCellRangeByName(range_str)

            # 设置背景颜色
            if background_color:
                # 将十六进制颜色转换为整数
                bg_color_int = int(background_color.replace("#", ""), 16)
                cell_range.CellBackColor = bg_color_int

            # 设置字体颜色
            if font_color:
                # 将十六进制颜色转换为整数
                font_color_int = int(font_color.replace("#", ""), 16)
                cell_range.CharColor = font_color_int

            # 设置粗体
            if bold is not None:
                cell_range.CharWeight = 150.0 if bold else 100.0  # 150.0 是粗体,100.0 是正常

            # 设置对齐方式
            if alignment:
                # 设置水平对齐方式
                struct = cell_range.getPropertyValue("HoriJustify")
                if alignment == "left":
                    struct.value = "LEFT"
                elif alignment == "center":
                    struct.value = "CENTER"
                elif alignment == "right":
                    struct.value = "RIGHT"
                cell_range.setPropertyValue("HoriJustify", struct)

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def create_chart(cls, chart_type, data_range, title=None, x_axis_title=None, y_axis_title=None):
        """
        Create a chart in the active worksheet based on the specified data range.

        Args:
            chart_type (str): Type of chart to create (bar, column, line, pie, scatter, area)
            data_range (str): Range containing the data for the chart, in the format of 'A1:B10'
            title (str, optional): Title for the chart
            x_axis_title (str, optional): Title for the X axis
            y_axis_title (str, optional): Title for the Y axis

        Returns:
            bool: True if successful, False otherwise
        """
        # 将图表类型映射到LibreOffice的图表类型常量
        try:
            chart_type_map = {
                "bar": "com.sun.star.chart.BarDiagram",
                "column": "com.sun.star.chart.ColumnDiagram",
                "line": "com.sun.star.chart.LineDiagram",
                "pie": "com.sun.star.chart.PieDiagram",
                "scatter": "com.sun.star.chart.ScatterDiagram",
                "area": "com.sun.star.chart.AreaDiagram",
            }

            # 获取数据范围
            cell_range_address = cls.sheet.getCellRangeByName(data_range).getRangeAddress()

            # 创建图表
            charts = cls.sheet.getCharts()
            rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
            rect.Width = 10000  # 默认宽度
            rect.Height = 7000  # 默认高度

            # 添加图表到工作表
            charts.addNewByName("MyChart", rect, (cell_range_address,), False, False)

            # 获取图表
            chart = charts.getByName("MyChart")
            chart_doc = chart.getEmbeddedObject()

            # 设置图表类型
            diagram = chart_doc.createInstance(chart_type_map[chart_type])
            chart_doc.setDiagram(diagram)

            # 设置图表标题
            if title:
                chart_doc.Title.String = title

            # 设置X轴标题
            if x_axis_title:
                chart_doc.Diagram.XAxis.AxisTitle.String = x_axis_title

            # 设置Y轴标题
            if y_axis_title:
                chart_doc.Diagram.YAxis.AxisTitle.String = y_axis_title

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def freeze_panes(cls, rows=0, columns=0):
        """
        冻结活动工作表中的行和/或列

        Args:
            rows (int): 从顶部开始冻结的行数
            columns (int): 从左侧开始冻结的列数

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 获取当前视图
            view = cls.doc.getCurrentController()

            # 设置冻结窗格
            view.freezeAtPosition(columns, rows)

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def rename_sheet(cls, old_name, new_name):
        """
        重命名工作表

        Args:
            old_name (str): 要重命名的工作表的当前名称
            new_name (str): 工作表的新名称

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 获取所有工作表
            sheets = cls.doc.getSheets()

            # 检查原工作表是否存在
            if not sheets.hasByName(old_name):
                return False

            # 检查新名称是否已存在
            if sheets.hasByName(new_name):
                return False

            # 获取要重命名的工作表
            sheet = sheets.getByName(old_name)

            # 重命名工作表
            sheet.setName(new_name)

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def copy_sheet(cls, source_sheet, new_sheet_name=None):
        """
        创建工作簿中现有工作表的副本

        Args:
            source_sheet (str): 要复制的工作表名称
            new_sheet_name (str, optional): 新工作表副本的名称,如果不提供则自动生成

        Returns:
            str: 新创建的工作表名称,如果失败则返回None
        """
        try:
            # 获取所有工作表
            sheets = cls.doc.getSheets()

            # 检查源工作表是否存在
            if not sheets.hasByName(source_sheet):
                return None

            # 如果没有提供新名称,则生成一个
            if not new_sheet_name:
                # 生成类似 "Sheet1 (2)" 的名称
                base_name = source_sheet
                counter = 1
                new_sheet_name = f"{base_name} ({counter})"

                # 确保名称不重复
                while sheets.hasByName(new_sheet_name):
                    counter += 1
                    new_sheet_name = f"{base_name} ({counter})"

            # 检查新名称是否已存在
            if sheets.hasByName(new_sheet_name):
                return None  # 名称已存在,无法创建

            # 获取源工作表的索引
            source_index = -1
            for i in range(sheets.getCount()):
                if sheets.getByIndex(i).getName() == source_sheet:
                    source_index = i
                    break

            if source_index == -1:
                return None

            # 复制工作表
            sheets.copyByName(source_sheet, new_sheet_name, source_index + 1)

            cls.ret = f"New sheet created: {new_sheet_name}"
            return new_sheet_name

        except Exception as e:
            cls.ret = f"Error: {e}"
            return None

    @classmethod
    def reorder_sheets(cls, sheet_name, position):
        """
        重新排序工作表在工作簿中的位置

        Args:
            sheet_name (str): 要移动的工作表名称
            position (int): 要移动到的位置(基于0的索引)

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 获取所有工作表
            sheets = cls.doc.getSheets()

            # 检查工作表是否存在
            if not sheets.hasByName(sheet_name):
                return False

            # 获取工作表总数
            sheet_count = sheets.getCount()

            # 检查位置是否有效
            if position < 0 or position >= sheet_count:
                return False

            # 获取要移动的工作表
            sheet = sheets.getByName(sheet_name)

            # 获取工作表当前索引
            current_index = -1
            for i in range(sheet_count):
                if sheets.getByIndex(i).Name == sheet_name:
                    current_index = i
                    break

            if current_index == -1:
                return False

            # 移动工作表到指定位置
            sheets.moveByName(sheet_name, position)

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def set_chart_legend_position(cls, position):
        """
        Set the position of the legend in a chart in the active worksheet.

        Args:
            position (str): Position of the legend ('top', 'bottom', 'left', 'right', 'none')

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取当前工作表中的所有图表
            charts = cls.sheet.getCharts()
            if charts.getCount() == 0:
                return False

            # 获取第一个图表(假设我们要修改的是第一个图表)
            chart = charts.getByIndex(0)
            chart_obj = chart.getEmbeddedObject()

            # 获取图表的图例
            diagram = chart_obj.getDiagram()
            legend = chart_obj.getLegend()

            # 根据指定的位置设置图例位置
            if position == "none":
                # 如果选择"none",则隐藏图例
                chart_obj.HasLegend = False
            else:
                # 确保图例可见
                chart_obj.HasLegend = True

                import inspect

                print(inspect.getmembers(legend))

                # 设置图例位置
                if position == "top":
                    pos = uno.Enum("com.sun.star.chart.ChartLegendPosition", "TOP")
                elif position == "bottom":
                    pos = uno.Enum("com.sun.star.chart.ChartLegendPosition", "BOTTOM")
                elif position == "left":
                    pos = uno.Enum("com.sun.star.chart.ChartLegendPosition", "LEFT")
                elif position == "right":
                    pos = uno.Enum("com.sun.star.chart.ChartLegendPosition", "RIGHT")

                legend.Alignment = pos

            cls.ret = "Success"
            return True
        except Exception:
            cls.ret = "Error"
            return False

    @classmethod
    def set_number_format(cls, range_str, format_type, decimal_places=None):
        """
        Apply a specific number format to a range of cells in the active worksheet.

        Args:
            range_str (str): Range to format, in the format of 'A1:B10'
            format_type (str): Type of number format to apply
            decimal_places (int, optional): Number of decimal places to display

        Returns:
            bool: True if successful, False otherwise
        """
        try:
            # 获取单元格范围
            cell_range = cls.sheet.getCellRangeByName(range_str)

            # 获取数字格式化服务
            number_formats = cls.doc.NumberFormats
            locale = cls.doc.CharLocale

            # 根据格式类型设置格式字符串
            format_string = ""

            if format_type == "general":
                format_string = "General"
            elif format_type == "number":
                if decimal_places is not None:
                    format_string = f"0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}"
                else:
                    format_string = "0"
            elif format_type == "currency":
                if decimal_places is not None:
                    format_string = f"[$¥-804]#,##0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}"
                else:
                    format_string = "[$¥-804]#,##0.00"
            elif format_type == "accounting":
                if decimal_places is not None:
                    format_string = f"_-[$¥-804]* #,##0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}_-;-[$¥-804]* #,##0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}_-;_-[$¥-804]* \"-\"_-;_-@_-"
                else:
                    format_string = '_-[$¥-804]* #,##0.00_-;-[$¥-804]* #,##0.00_-;_-[$¥-804]* "-"??_-;_-@_-'
            elif format_type == "date":
                format_string = "YYYY/MM/DD"
            elif format_type == "time":
                format_string = "HH:MM:SS"
            elif format_type == "percentage":
                if decimal_places is not None:
                    format_string = f"0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}%"
                else:
                    format_string = "0.00%"
            elif format_type == "fraction":
                format_string = "# ?/?"
            elif format_type == "scientific":
                if decimal_places is not None:
                    format_string = f"0{('.' + '0' * decimal_places) if decimal_places > 0 else ''}E+00"
                else:
                    format_string = "0.00E+00"
            elif format_type == "text":
                format_string = "@"

            # 获取格式键
            format_key = number_formats.queryKey(format_string, locale, True)

            # 如果格式不存在,则添加
            if format_key == -1:
                format_key = number_formats.addNew(format_string, locale)

            # 应用格式
            cell_range.NumberFormat = format_key

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def adjust_column_width(cls, columns, width=None, autofit=False):
        """
        调整活动工作表中指定列的宽度

        Args:
            columns (str): 要调整的列范围,例如 'A:C' 表示从A列到C列
            width (float, optional): 要设置的宽度(以字符为单位)
            autofit (bool, optional): 是否自动调整列宽以适应内容

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 解析列范围
            col_range = columns.split(":")
            start_col = cls._column_name_to_index(col_range[0])

            if len(col_range) > 1:
                end_col = cls._column_name_to_index(col_range[1])
            else:
                end_col = start_col

            # 获取列对象
            columns_obj = cls.sheet.getColumns()

            # 遍历指定的列范围
            for col_idx in range(start_col, end_col + 1):
                column = columns_obj.getByIndex(col_idx)

                if autofit:
                    # 自动调整列宽
                    column.OptimalWidth = True
                elif width is not None:
                    # 设置指定宽度(转换为1/100毫米)
                    # 大约一个字符宽度为256 (1/100 mm)
                    column.Width = int(width * 256)

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def adjust_row_height(cls, rows, height=None, autofit=False):
        """
        调整活动工作表中指定行的高度

        Args:
            rows (str): 要调整的行范围,例如 '1:10' 表示第1行到第10行
            height (float, optional): 要设置的高度(以点为单位)
            autofit (bool, optional): 是否自动调整行高以适应内容

        Returns:
            bool: 操作成功返回True,否则返回False
        """
        try:
            # 解析行范围
            row_range = rows.split(":")
            start_row = int(row_range[0])
            end_row = int(row_range[1]) if len(row_range) > 1 else start_row

            # 获取行对象
            for row_index in range(start_row, end_row + 1):
                row = cls.sheet.getRows().getByIndex(row_index - 1)  # 索引从0开始

                if autofit:
                    # 自动调整行高以适应内容
                    row.OptimalHeight = True
                elif height is not None:
                    # 设置指定高度(将点转换为1/100毫米,LibreOffice使用的单位)
                    # 1点 ≈ 35.28 1/100毫米
                    row.Height = int(height * 35.28)
                    row.OptimalHeight = False

            cls.ret = "Success"
            return True
        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def export_to_pdf(cls, file_path=None, sheets=None, open_after_export=False):
        """
        将当前文档或指定工作表导出为PDF文件

        Args:
            file_path (str, optional): PDF文件保存路径,如果不指定则使用当前文档路径
            sheets (list, optional): 要包含在PDF中的工作表名称列表,如果不指定则包含所有工作表
            open_after_export (bool, optional): 导出后是否打开PDF文件

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 如果未指定文件路径,则使用当前文档路径并更改扩展名为.pdf
            if not file_path:
                if cls.doc.hasLocation():
                    url = cls.doc.getLocation()
                    file_path = uno.fileUrlToSystemPath(url)
                    file_path = os.path.splitext(file_path)[0] + ".pdf"
                else:
                    # 如果文档尚未保存,则在用户桌面创建临时文件
                    desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
                    file_path = os.path.join(desktop_path, "LibreOffice_Export.pdf")

            # 确保文件路径是系统路径,然后转换为URL
            pdf_url = uno.systemPathToFileUrl(os.path.abspath(file_path))

            # 创建导出属性
            export_props = []

            # 设置过滤器名称
            export_props.append(PropertyValue(Name="FilterName", Value="calc_pdf_Export"))

            # 如果指定了特定工作表,则只导出这些工作表
            if sheets and isinstance(sheets, list) and len(sheets) > 0:
                # 获取所有工作表
                all_sheets = cls.doc.getSheets()
                selection = []

                # 查找指定的工作表
                for sheet_name in sheets:
                    if all_sheets.hasByName(sheet_name):
                        sheet = all_sheets.getByName(sheet_name)
                        selection.append(sheet)

                # 如果找到了指定的工作表,则设置导出选择
                if selection:
                    export_props.append(PropertyValue(Name="Selection", Value=tuple(selection)))

            # 导出PDF
            cls.doc.storeToURL(pdf_url, tuple(export_props))

            # 如果需要,导出后打开PDF
            if open_after_export:
                if sys.platform.startswith("darwin"):  # macOS
                    subprocess.call(("open", file_path))
                elif os.name == "nt":  # Windows
                    os.startfile(file_path)
                elif os.name == "posix":  # Linux
                    subprocess.call(("xdg-open", file_path))

            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False

    @classmethod
    def set_zoom_level(cls, zoom_percentage):
        """
        调整当前工作表的缩放级别,使单元格看起来更大或更小

        Args:
            zoom_percentage (int): 缩放级别的百分比(例如,75表示75%,100表示正常大小,150表示放大)。
                                有效范围通常为10-400。

        Returns:
            bool: 成功返回True,失败返回False
        """
        try:
            # 获取当前控制器
            controller = cls.doc.getCurrentController()

            # 设置缩放值
            # 确保缩放值在合理范围内
            if zoom_percentage < 10:
                zoom_percentage = 10
            elif zoom_percentage > 400:
                zoom_percentage = 400

            # 应用缩放值
            controller.ZoomValue = zoom_percentage
            cls.ret = "Success"
            return True

        except Exception as e:
            cls.ret = f"Error: {e}"
            return False


if __name__ == "__main__":
    print(CalcTools._get_column_index("A"))
    print(CalcTools.get_workbook_info())
    print(CalcTools.get_content())
    CalcTools.switch_active_sheet("Sheet2")
    # helper.set_column_values('A', [1, 2, 3, 4, 5])
    # helper.highlight_range('A1:A3', 'Red')
    # helper.transpose_range('A1:D5', 'B8')
    print(CalcTools.get_column_data("A"))
    CalcTools.sort_column("A", True)
    CalcTools.hide_row_data("N/A")
    CalcTools.reorder_columns(["B", "A", "C"])
    CalcTools.freeze_panes(1, 1)
    # helper.set_validation_list('C', ['Pass', 'Fail', 'Held'])
    CalcTools.export_to_csv()