Вставка рисунка на место выделения текста

Автор Ципихович Эндрю, 16 марта 2026, 12:34

0 Пользователи и 1 гость просматривают эту тему.

Ципихович Эндрю

здравствуйте, собственно сабж, вставляю так:
import uno
# from com.sun.star.awt import XMouseClickHandler
# import unohelper
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER # Модуль функции вставки изображения
import winsound # Модуль для воспроизводства звукового сигнала
.............
# Функция вставки изображения
def TextSelectedInsertSVG():
    # Путь к файлу изображения
    image_path = r"C:\Users\start\AppData\Roaming\LibreOffice\4\user\Scripts\python\output.svg"
    # Преобразование локального пути в URL, понятный LibreOffice
    graphic_url = uno.systemPathToFileUrl(image_path)
    # Получаем текущий курсор документа
    cursor = doc.CurrentController.getViewCursor()
    # Создаём графический объект
    graphic_object = doc.createInstance("com.sun.star.text.GraphicObject")
    # Устанавливаем URL изображения
    graphic_object.GraphicURL = graphic_url
    # Определяем тип привязки графика ("AS_CHARACTER" означает вставку как символ)
    graphic_object.AnchorType = AS_CHARACTER
    # Добавляем графику в документ
    doc.Text.insertTextContent(cursor, graphic_object, False)
    # Воспроизводим звуковой сигнал
    winsound.MessageBeep(winsound.MB_OK)
вставляется почему-то не на место выделенного текста а аккурат слева от выделения
2 стрелка "Отменить" становиться активной ХЗ пойми после чего, вроде после дабл клика мыши в любом месте, как побороть эти две проблемки?
PS всё тоже в прилагаемом файле
спасибо

sokol92

#1
Не вижу (пока) загадок.
1. Дважды щелкаем по "формуле" (диапазоне текста, заключенном в знаки доллара). Слева добавляется рисунок, тип привязки "как символ". Наш слушатель событий сообщил, что всё обработано, поэтому мы сразу не видим на экране новую стрелку "Отменить". Чтобы стрелка появилась, достаточно щелкнуть мышью по любому месту экрана.

2. Основной вопрос: что такое "вставить на место выделения текста"? Удалить текст и затем вставить рисунок исходного размера (или в обратной последовательности)?
Будет лучше всего, если Вы вручную выполните желаемые действия и выгрузите файл-результат.
Владимир.

Ципихович Эндрю

Цитата: sokol92 от 16 марта 2026, 15:45Чтобы стрелка появилась, достаточно щелкнуть мышью по любому месту экрана
и подождать энное количество мс, так нельзя принудительно стрелку вразумить-чтобы она уже указывала, что можно отменить? чтобы картинка совпадала с тем, что происходит в голове у юзера))
Цитата: sokol92 от 16 марта 2026, 15:45вставить рисунок исходного размера
-пока просто рисунок по факту - то что есть, потому как "исходный размер" - тоже витиевато))
Цитата: sokol92 от 16 марта 2026, 15:45Будет лучше всего, если Вы вручную выполните желаемые действия и выгрузите файл-результат
ок






sokol92

Посмотрите вложенный файл.

Замечания:

1. Путь к вставляемому изображению формулы:
image_path = r"C:\Temp\output.svg"
Измените его, если нужно.

2. Слушатель в нашем случае должен возвращать все-таки False (у меня были ложные ассоциации с Excel).

Владимир.

Ципихович Эндрю

Цитата: Ципихович Эндрю от 17 марта 2026, 08:16так нельзя принудительно стрелку вразумить-чтобы она уже указывала, что можно отменить?
sokol92, спасибо за пример, спору нет вразумили на +2, теперь для отмены юзеру нужно будет сделать два телодвижения
думал просто заремарчить строку formula.setString(""), ан нет...
опять
Цитата: Ципихович Эндрю от 16 марта 2026, 12:34вставляется почему-то не на место выделенного текста а аккурат слева от выделения
как уладить, чтобы для отмены юзеру нужно будет сделать одно телодвижение?
Цитата: Ципихович Эндрю от 17 марта 2026, 08:16чтобы картинка совпадала с тем, что происходит в голове у юзера))
))


sokol92

Цитата: Ципихович Эндрю от 18 марта 2026, 15:48как уладить, чтобы для отмены юзеру нужно будет сделать одно телодвижение?
Изложить соответствующий фрагмент кода в такой редакции:
                undo_manager = doc.getUndoManager()
                undo_manager.enterUndoContext("Insert formula")
                formula.setString("")
                text_selected_insert_image(formula)
                undo_manager.leaveUndoContext()
                controller.select(formula)
                return False
Владимир.

Ципихович Эндрю

sokol92, бывает кофе-а это кофе с молоком, спасибо работает, финалим

Ципихович Эндрю

Цитата: Ципихович Эндрю от 19 марта 2026, 07:08sokol92, бывает кофе-а это кофе с молоком
а далее ещё может быть кофе со сливками))
хотел помечать вставленное изображение значением формулы, код:

import uno
from com.sun.star.awt import XMouseClickHandler
import unohelper
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER # Модуль функции вставки изображения
import winsound # Модуль для воспроизводства звукового сигнала

image_path = r"C:\Users\start\AppData\Roaming\LibreOffice\4\user\Scripts\python\output.svg"

ctx = uno.getComponentContext()
smgr = ctx.ServiceManager
doc = None
controller = None
handler = None
debug = False


def Message_box(title, message):
    try:
        toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
        desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        frame = desktop.getCurrentFrame()
        if frame:
            # Воспроизводим звуковой сигнал
            winsound.MessageBeep(winsound.MB_OK)
            window = frame.getContainerWindow()
            msgbox = toolkit.createMessageBox(window, "infobox", 1, title, message)
            msgbox.execute()
            msgbox.dispose()
        else:
            print(f"{title}: {message}")
    except Exception as e:
        print(f"Ошибка показа сообщения: {e}")

# Функция вставки изображения
def text_selected_insert_image(cursor, form):
    graphic_url = uno.systemPathToFileUrl(image_path)
    graphic_object = doc.createInstance("com.sun.star.text.GraphicObject")
    graphic_object.GraphicURL = graphic_url
    graphic_object.AnchorType = AS_CHARACTER
    # Присваиваем значение переменной form свойству OriginURL у вставляемого изображения, в дальнейшем её можно будет считать
    graphic_object.setPropertyValue("OriginURL", form)
    doc.Text.insertTextContent(cursor, graphic_object, False)
    # Воспроизводим звуковой сигнал
    winsound.MessageBeep(winsound.MB_OK)
   
class MouseHandler(unohelper.Base, XMouseClickHandler):
    def __init__(self):
        self.click_count = 0

    def mousePressed(self, ev):
        return False

    def mouseReleased(self, ev):
        if ev.ClickCount != 2:
            return False

        selection = controller.getSelection()
        try:
            t_range = controller.getSelection().getByIndex(0).getStart()
        except Exception:
            return False

        pattern = r"\$(?:\\.|[^$])*(?<!\\)\$"
        formulas = find_all_in_text_range(doc, t_range.TextParagraph, pattern, True)
        text = doc.getText()
        for formula in formulas:
            if (
                text.compareRegionStarts(formula.getStart(), t_range) >= 0
                and text.compareRegionEnds(formula.getEnd(), t_range) < 0
            ):
                # Получаем менеджер отмены действий документа
                undo_manager = doc.getUndoManager()
                # Начинаем новую группу отменяемых действий с названием "Insert formula"
                undo_manager.enterUndoContext("Insert formula")
                form = formula.String
                # Очищаем строку формулы перед вставкой нового содержимого
                formula.setString("")
                # Message_box("Сообщение", f"Значение переменной form: {form}")                 
                # Функция вставляет изображение в выбранное место текста
                text_selected_insert_image(formula, form)
                # Завершаем группу отменяемых действий
                undo_manager.leaveUndoContext()
                # Перемещаем курсор вправо непосредственно после вставленной формулы
                controller.select(formula)
                # Возвращаем значение False
                return False
        return False

    def disposing(self, ev):
        pass


def start_mouse_handler(_=None):
    global doc, controller, handler
    try:
        desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        doc = desktop.getCurrentComponent()
        if doc is None:
            Message_box("Ошибка", "Документ не найден")
            return
        controller = doc.CurrentController
        if handler is None:
            handler = MouseHandler()
        controller.addMouseClickHandler(handler)

    except Exception as e:
        Message_box("Критическая ошибка", str(e))


def find_all_in_text_range(doc, text_range, search_string, is_regexp=None):

    descr = doc.createSearchDescriptor()
    descr.setPropertyValue("SearchRegularExpression", (is_regexp == True))
    descr.setSearchString(search_string)
    mri(descr)

    arr = []
    text = doc.getText()
    found = doc.findNext(text_range.getStart(), descr)
    mri(found)

    while (
        not found is None
        and text.compareRegionEnds(found.getEnd(), text_range.getEnd()) >= 0
    ):
        arr.append(found)
        found = doc.findNext(found.getEnd(), descr)

    return arr


def mri(obj):
    if not debug:
        return
    mri_instance = smgr.createInstanceWithContext("mytools.Mri", ctx)
    mri_instance.inspect(obj)
    Message_box("mri вызван", "")


g_exportedScripts = (start_mouse_handler,)
ошибки нет, и не вставляет картинку, вместо формулы получается пустое место, ЧЯДНТ?

sokol92

Вы присваиваете свойство OriginURL - как минимум, значение свойства должно иметь URL формат.

Вы на место "формулы" (фрагмента текста, заключенного в знаки доллара) вставляете изображение в формате .svg (которое где-то сгенерировано). Логично предположить, что файл .svg где то в комментарии  (a это xml файл) содержит текст исходной формулы (в Вашем примере это именно так). Можно подумать, как этот комментарий получить при необходимости.

В версии 26.2 (когда реализован pip install) мы получаем возможность внутри LibreOffice генерировать .svg файл, соответствующий LaTeX формуле (установив дополнительные пакеты Python).
Владимир.

Ципихович Эндрю

тут про пометку изображения - чуть рановато, опять буксую, решил работающий макрос разделить на два - вот тот второй=последний, который на выделенное место вставит формулу:
import uno       # Universal Network Objects - мост между Python и API LibreOffice
import winsound  # Модуль для воспроизведения звукового сигнала
import unohelper # Модуль для работы с UNO (Universal Network Objects) — компонентной моделью LibreOffice
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER

image_path = r"C:\Users\start\AppData\Roaming\LibreOffice\4\user\Scripts\python\output.svg"

def Message_box(title, message):
    try:
        ctx = uno.getComponentContext()
        smgr = ctx.ServiceManager
        toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
        desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        frame = desktop.getCurrentFrame()
        if frame:
            winsound.MessageBeep(winsound.MB_OK) # Воспроизводим звуковой сигнал до появления формы
            window = frame.getContainerWindow()
            msgbox = toolkit.createMessageBox(window, "infobox", 1, title, message) # Создать в памяти заготовку для окна
            msgbox.execute()                     # Нарисовать окно на экране, ждать
            msgbox.dispose()                     # Пользователь нажал ОК, теперь нужно стереть это окно из памяти
        else:
            print(f"{title}: {message}")
    except Exception as e:
        print(f"Ошибка показа сообщения: {e}")

def InsertImage():
    ctx = uno.getComponentContext()
    desktop = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
    doc = desktop.getCurrentComponent()
    controller = doc.getCurrentController()
    # Получаем текущее выделение
    selection = controller.getSelection()
    # Проверяем, что выделение поддерживает XTextRange
    if not selection.supportsService("com.sun.star.text.TextRange"):
        Message_box("Сообщение", "not selection.supportsService(com.sun.star.text.TextRange)")
        # Если нет — выходим из макроса
        return
    # Начинаем группу отменяемых действий
    undo_manager = doc.getUndoManager()
    undo_manager.enterUndoContext("Insert Image")
    # Вставляем изображение на место выделения
    graphic_url = uno.systemPathToFileUrl(image_path)
    graphic_object = doc.createInstance("com.sun.star.text.GraphicObject")
    graphic_object.GraphicURL = graphic_url
    graphic_object.AnchorType = AS_CHARACTER
    doc.Text.insertTextContent(selection, graphic_object, False) # Заменяем выделенный текст изображением
    winsound.MessageBeep(winsound.MB_OK) # Воспроизводим звуковой сигнал
    # Завершаем группу отменяемых действий
    undo_manager.leaveUndoContext()
    # Перемещаем курсор вправо непосредственно после вставленной формулы
    controller.select(graphic_object)

g_exportedScripts = InsertImage,
пока не проходит проверку, получаю not selection.supportsService(com.sun.star.text.TextRange)
как подправить?

sokol92

Выделение фрагмента(ов) текста (бывают и другие типы выделений) текстового документа поддерживает интерфейс TextRanges.
С помощью свойства count можно проверить, сколько фрагментов текста выделено, с помощью метода getByIndex получить фрагменты (которые поддерживают интерфейс TextRange).
Обратите внимание на конструкцию в скриптах из предыдущих сообщений:
controller.getSelection().getByIndex(0)
Владимир.

Ципихович Эндрю

ну ок, инет подсказывает
controller.getSelection().getByIndex(0)Пояснение
controller — это объект-контроллер текущего документа (например, текстового документа Writer). Он управляет выделением, курсором и другими аспектами взаимодействия с документом.
controller.getSelection() — возвращает текущий выделенный объект (selection). В Writer это может быть диапазон текста, таблица, изображение и т. д., в зависимости от того, что выделено.
.getByIndex(0) — у объекта выделения (selection) есть метод getByIndex, который позволяет получить конкретный элемент из выделения по его индексу. Индекс 0 означает первый (и часто единственный) выделенный элемент
на ум приходит только текст
# Получаем текущее выделение
selection = controller.getSelection()
заменить на
# Получаем текущее выделение
selection = controller.getSelection().getByIndex(0)
и тут всё по кругу - вставляет картинку не на место выделенного, проблемы с кнопкой Отменить...
потому как глаз замылился))
sokol92, можно Вас попросить отредактировать макрос в сообщении № 9
PS надеюсь, что понятно, что этот макрос из поста № 9 запускаю с клавиатуры после того как формула уже выделена


Ципихович Эндрю

одно лечим-другое калечим)) вставляет куда нужно!!
def InsertImage():
    try:
        ctx = uno.getComponentContext()
        desktop = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        doc = desktop.getCurrentComponent()
        controller = doc.getCurrentController()
        selection = controller.getSelection()
        # Проверяем, что выделение поддерживает интерфейс TextRanges
        if not selection.supportsService("com.sun.star.text.TextRanges"):
            Message_box("Ошибка", "Выделение не поддерживает интерфейс TextRanges.")
            return
        # Получаем первый выделенный фрагмент (диапазон текста)
        text_range = selection.getByIndex(0)
        # Начинаем группу отменяемых действий
        undo_manager = doc.getUndoManager()
        undo_manager.enterUndoContext("Insert Image")
        # Удаляем выделенный текст
        text_range.setString("")
        # Вставляем изображение в пустое место, где был текст
        graphic_url = uno.systemPathToFileUrl(image_path)
        graphic_object = doc.createInstance("com.sun.star.text.GraphicObject")
        graphic_object.GraphicURL = graphic_url
        graphic_object.AnchorType = AS_CHARACTER
        # Вставляем объект в диапазон, из которого только что удалили текст
        doc.Text.insertTextContent(text_range, graphic_object, False)
        winsound.MessageBeep(winsound.MB_OK) # Воспроизводим звуковой сигнал
        # Завершаем группу отменяемых действий
        undo_manager.leaveUndoContext()
    except Exception as e:
        Message_box("Ошибка выполнения", f"Произошла ошибка: {e}")
но стрелка отменить - опять выёживается, как её приструнить? вроде группа отменяемых действий на месте....

Ципихович Эндрю

#13
sokol92, неужели
# Возвращаем значение False
        return False
имеет значение? какое? - я считаю что не имеет))
по-моему уладил, хотя как-то всё равно корявенько, в тоже время читаю свой пост-было кофе с молоком)), может на радостях....
уже ничего более не улучшить?

import uno       # Universal Network Objects - мост между Python и API LibreOffice
import winsound  # Модуль для воспроизведения звукового сигнала
import unohelper # Модуль для работы с UNO (Universal Network Objects) — компонентной моделью LibreOffice
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER

image_path = r"C:\Users\start\AppData\Roaming\LibreOffice\4\user\Scripts\python\output.svg"

def Message_box(title, message):
    try:
        ctx = uno.getComponentContext()
        smgr = ctx.ServiceManager
        toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
        desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        frame = desktop.getCurrentFrame()
        if frame:
            winsound.MessageBeep(winsound.MB_OK) # Воспроизводим звуковой сигнал до появления формы
            window = frame.getContainerWindow()
            msgbox = toolkit.createMessageBox(window, "infobox", 1, title, message) # Создать в памяти заготовку для окна
            msgbox.execute()                     # Нарисовать окно на экране, ждать
            msgbox.dispose()                     # Пользователь нажал ОК, теперь нужно стереть это окно из памяти
        else:
            print(f"{title}: {message}")
    except Exception as e:
        print(f"Ошибка показа сообщения: {e}")

def InsertImage():
    try:
        ctx = uno.getComponentContext()
        desktop = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
        doc = desktop.getCurrentComponent()
        controller = doc.getCurrentController()
        selection = controller.getSelection()
        # Проверяем, что выделение поддерживает интерфейс TextRanges
        if not selection.supportsService("com.sun.star.text.TextRanges"):
            Message_box("Ошибка", "Выделение не поддерживает интерфейс TextRanges.")
            return
        # Получаем первый выделенный фрагмент (диапазон текста)
        text_range = selection.getByIndex(0)
        # Получаем менеджер отмены действий документа
        undo_manager = doc.getUndoManager()
        # Начинаем группу отменяемых действий
        undo_manager.enterUndoContext("Insert Image")
        # Удаляем выделенный текст
        text_range.setString("")
        # Вставляем изображение в пустое место, где был текст
        graphic_url = uno.systemPathToFileUrl(image_path)
        graphic_object = doc.createInstance("com.sun.star.text.GraphicObject")
        graphic_object.GraphicURL = graphic_url
        graphic_object.AnchorType = AS_CHARACTER
        # Вставляем объект в диапазон, из которого только что удалили текст
        doc.Text.insertTextContent(text_range, graphic_object, False)
        winsound.MessageBeep(winsound.MB_OK) # Воспроизводим звуковой сигнал
        # Завершаем группу отменяемых действий
        undo_manager.leaveUndoContext()
        # Перемещаем курсор вправо непосредственно после вставленной формулы
        controller.select(graphic_object)
        # Возвращаем значение False
        return False
    except Exception as e:
        Message_box("Ошибка выполнения", f"Произошла ошибка: {e}")

g_exportedScripts = InsertImage,

sokol92

Возвращаемое значение было важно при обработке события mouseReleased.

Значение, возвращаемое Вашей новой функцией InsertImage, нигде не анализируется.
Владимир.