Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Qt: QTextEdit/QTextBrowser - как сохранять позицию в тексте при resize?  (Прочитано 23037 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« : 11-05-2013 22:35 » new

Есть довольно длинные многострочные тексты (десятки килобайт + недо-html гипертекстовая разметка), отображаемые QTextBrowser в read-only. Основное назначение - последовательно читать эти самые тексты, прокручивая мышью, преимущественно.

В общем-то, важно, что строк много, и что строки длинные - происходит перенос.

Отсюда проблема: при изменении ширины контрола сбивается позиция текста, показываемого в окне. А хочется, чтобы как в notepad++, место, с которого текст отображается в виджете, при масштабировании оставалось неизменным.
Пока простого решения не нашёл - может, кто сталкивался?
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 11-05-2013 23:29 » 

А что доступно в операционном смысле (какие структурные элементы, какие параметры и какие операции над ними)? Просто виджет как чёрный ящик и позиция scroll у него?

Если хотя бы длина и ширина текста известны, то позицию можно попробовать вычислить, анализируя зависимость изменения длины от ширины.
« Последнее редактирование: 11-05-2013 23:31 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #2 : 12-05-2013 07:32 » 

Позицию попробовать вычислить.

Но... это если font-size, font-wieght, font-family в тексте одинаковы. И если не вмешиваются другие стили элементов

Доступна ли позиция курсора в readonly?
Попробовать привязать ее к scroll
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 12-05-2013 08:27 » 

Всё доступно (текст форматирую и вывожу сам поэлементно), фонт одинаковый (пока, хотя как знать, куда заведёт).
Если хотя бы длина и ширина текста известны, то позицию можно попробовать вычислить, анализируя зависимость изменения длины от ширины.
Не совсем понятно: фонт же не моноширинный, а переносы по словам делаются: чуть растянул, и перенос уже на другом слове - пойди угадай, в какой строке это произойдёт, в какой - нет.

Попробовать привязать ее к scroll
Да, такая мысль тоже была, просто думал, может, проще как-то решается - вроде бы, требование к виджету вполне разумное, чтобы он без прикручивания самописного наблюдателя за скролом мог корректно resize обрабатывать. Должны же быть готовые (хоть наполовину) решения?
Записан
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #4 : 12-05-2013 08:41 » 

Цитата
может, проще как-то решается - вроде бы, требование к виджету вполне разумное,
Даже браузеры не отслеживают focus при ресайзе. А ведь могли бы.


У меня на книШке (prestige) при повороте страницы теряется фокус на текущую страницу (frame), если размер шрифта не normal, а там ведь они далеко не ушли от textBrowser
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
zubr
Гость
« Ответ #5 : 12-05-2013 09:41 » 

В браузере через DOM можно было бы получить элемент в топе, его топовую позицию, через скрипт поставить ему анкор и по событию ресайза делать переход на анкор.
Записан
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #6 : 12-05-2013 09:52 » 

Вот что подумалось...
Есть какой-либо метод search текста в виджете
Т.е. поиск - установить position в видимом фрейме.
И  немного обыграв, при ресайзе все время устанавливать position в видимом фрейме
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 12-05-2013 09:53 » 

Цитата: Вад
Не совсем понятно: фонт же не моноширинный, а переносы по словам делаются: чуть растянул, и перенос уже на другом слове - пойди угадай, в какой строке это произойдёт, в какой - нет.
Ну scroll'у же пофиг, какие там где переносы. Он выражает либо относительную позицию курсора, либо относительную позицию видимого окна относительно высоты всего текста.

Если у тебя в результате resize высота текста изменилась в k=h(i+1)/h(i) раз, относительная позиция курсора (в длине текста, развёрнутого в одну строку) с при этом не изменится, а вот относительную позицию окна придётся поправить, поскольку видимая часть текста p изменилась в 1/k раз p(i+1)=p(i)/k.

Если курсора нет, то считать позицией курсора можно центр видимого окна. Позиция верха видимой части окна будет w=c-p/2. Следовательно w(i+1)=c-p(i)/(2*k).

Все величины относительные (от 0 до 1) и вещественные.

Погрешности будут обязательно. Например, текущая позиция у тебя стоит в таком месте, где абзацы короткие без переносов. Следовательно, при расширении окна содержимое видимой части не сожмётся по высоте, а в невидимых частях текста сожмётся, и тогда, подвивинув w, ты сдвинешь видимый текст. Главный вопрос, насколько сильно, и насколько часта и критична такая ошибка.

Я вон в Chrome смотрю на текущую страницу - нифига он при resize не точен. Пока пишу, scroll в самом конце, но браузер не знает, что желаемая пользователем позиция в самом конце, он ориентируется по верхней части видимого окна, у которой позиция примерно 0.6. При сжатии окна по горизонтали поле ввода, куда я пишу, уезжает вниз в невидимую часть, поскольку браузер корректирует позицию верха видимой части, а низ при этом подтягивается к ней.
« Последнее редактирование: 12-05-2013 09:55 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #8 : 13-05-2013 03:32 » 

Вад, прицепи проектик, где можно опробовать

но заранее могу сказать - даже Qt-креаторская справка так же сбивается, думаю, что причины такие же. Наверное, придётся помнить высоту каждой строки (с учётом переносов) и к этой координате делать перемотку

Добавлено через 1 минуту и 13 секунд:
как вариант - помнить, какая строка и какой по счёту знак были на текущем курсоре, а после ресайза эту строку помещать по вертикали в это же место
« Последнее редактирование: 13-05-2013 03:35 от Алексей++ » Записан

Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 15-05-2013 23:06 » 

Экспериментами занимаюсь в свободное время, когда хватает сил, поэтому продвигается по чайной ложке. Кое-какие результаты есть.

Вад, прицепи проектик, где можно опробовать
Да я, собственно, упражняюсь на абсолютно пустом проекте: одно окно с QTextBrowser, куда из текстового файла загружаю содержимое.

Цитата
но заранее могу сказать - даже Qt-креаторская справка так же сбивается, думаю, что причины такие же.
вот это-то и удивляет больше всего - раньше как-то не задумывался, а ведь это в плане юзабилити полный швах.

Цитата
как вариант - помнить, какая строка и какой по счёту знак были на текущем курсоре, а после ресайза эту строку помещать по вертикали в это же место
Так и пытаюсь делать. Пока получается до смешного противоположно ожиданиям: я думал, что позицию точно вычислить сложновато, а по факту нерешённой пока остаётся задача, как бы точно спозиционировать после ресайза.

В общем, на данный момент делаю так: вешаю свой слот на сигнал скролл-бара о смене позиции, и там делаю hitTest у layout-а, получая точную позицию курсора в начале самой верхней видимой в окне строки
Код: (C++)
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QScrollBar * scrollBar = ui->textBrowser->verticalScrollBar();

    QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
                          this, SLOT(onScrollValueChanged(int)));
}

void MainWindow::onScrollValueChanged( int value )
{
    QPointF pt(0, value);
    QTextDocument * doc = ui->textBrowser->document();

    QTextCursor cursor = ui->textBrowser->cursorForPosition(QPoint(0, 0)); // берём позицию курсора для левой верхней точки viewport
    int pos = cursor.position();
    // сохраняю pos в поле объекта - проверено, оно указывает точно в нужную позицию
    this->cursorPos = pos;
}
Можно, в принципе, и сам курсор сохранять, чтоб потом оставалось только установить его.
Сначала, пока с cursorForPosition варианта не увидел, пробовал ещё брать позицию как pos = doc->documentLayout()->hitTest(QPointF(0, value/*scroll pos*/), Qt::FuzzyHit) - в принципе, абсолютно тот же результат, и, может, даже одним и тем же путём получается - пока не разбирался, есть ли разница по скорости, визуально нет проблем.

С восстановлением позиции ситуация, честно говоря, пока дурацкая: я удивлён тем, что не наблюдаю в Qt никакого способа открутить прокрутку точно в точку, где находится курсор. Есть только мутный метод QTextEdit::ensureCursorVisible() - он лишь обещает, что виджет отскроллится как-то так, что текущая позиция курсора в кадр попадёт, но такое позиционирование весьма грубое. То есть, пока костыль выглядит так:
Код: (C++)
void MainWindow::resizeEvent ( QResizeEvent * event )
{
    Q_UNUSED(event);
    // изменяем и применяем курсор
    QTextCursor cursor = ui->textBrowser->textCursor();
    cursor.setPosition(cursorPos);
    ui->textBrowser->setTextCursor(cursor);
    // скролл:
    ui->textBrowser->ensureCursorVisible();
}
Кроме того, ещё надо проверять, чтобы в процессе resize не сохранялся результат onScrollValueChanged, либо программно скроллить только по завершении resize - я этого пока не делаю, но при грубом позиционировании это делать надо: иначе цепочка resize-ов сопровождается цепочкой "грубых" scroll-ов, и немного вразнос позиция уходит.

Но это, полагаю, неокончательный вариант - бывают случаи, когда, скажем, первая видимая строка после ресайза оказывается в самом низу, почти скрытая. Пока есть на примете костыль с пошаговым скроллом на несколько шагов вверх-вниз от "первого приближения", полученного ensureCursorVisible, как вариант, тем же бинарным поиском, пока не попаду в искомую позицию достаточно точно (как оказалось, получить значение текущей позиции проще, чем в ней оказаться Улыбаюсь)

Либо, как альтернативный (и извращённый) вариант, вставлять в начале каждой строки по html-якорю #<line_num> и делать QTextEdit::scrollToAnchor с номером строки.

Первый вариант лучше - можно сделать свой кастомный контрол, унаследовав от стандартного и допилив поведение.
« Последнее редактирование: 15-05-2013 23:12 от Вад » Записан
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #10 : 16-05-2013 03:59 » 

Вад, могу предложить более сумасшедший вариант ))

Связка   таблица (1 колонка, туча строк) -> модель -> делегат (одна ячейка содержит один QTextEdit/QTextBrowser)

всегда будешь знать, какую ячейку нужно явить пред светлы очи после ресайза

поэкспериментировать прямо сейчас тоже не могу, попозже только
Записан

Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #11 : 16-05-2013 08:21 » 

Цитата: Вад
я удивлён тем, что не наблюдаю в Qt никакого способа открутить прокрутку точно в точку, где находится курсор.
Дак и в WinAPI то же самое. Это всё оттого, что скролл работает с абстрактным окном (его содержимым) и про курсор ничего не знает, потому что это более общий случай. А вот почему ни один редактор (как контрол) не озаботился средствами сообщить позицию курсора в координатах содержимого окна - это, конечно, вопрос интересный. Я когда окну в Windows пытался прилепить custom-скроллбары, тоже с этим намучился изрядно. Так нормально оно и не работает до сих пор.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #12 : 16-05-2013 10:17 » 

А вот почему ни один редактор (как контрол) не озаботился средствами сообщить позицию курсора в координатах содержимого окна - это, конечно, вопрос интересный.

Это, скорей всего, потому, что нет жёсткой привязки текстовой информации к координатам окна. Проще говоря, инфа - картинка, как результат отрисовки, которая зависит от выбранного шрифта и его свойств.
Мы давно уже махнули рукой на стандартные редакторы, пишем свои. Ох и мороки с ними.
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #13 : 16-05-2013 10:54 » 

Вад, могу предложить более сумасшедший вариант ))
Связка   таблица (1 колонка, туча строк) -> модель -> делегат (одна ячейка содержит один QTextEdit/QTextBrowser)
А тормозить оно не будет?  Улыбаюсь Допустим, килобайт 200 - это где-то порядка 500-1000 элементов на один документ получится (абзацы небольшие), а если таких окон открыть штук 5-6, как предполагается... Можно, конечно, ещё и кэшируемую модель делать с загрузкой по требованию, но тут я не знаю, как точно Qt будет поступать с невидимыми виджетами-делегатами (типовой сценарий - что юзер свои окна вдоль и поперёк скроллить будет, да ещё и по гиперссылкам в них ходить взад-вперёд).

Всё-таки, пока склоняюсь к тому, чтобы скроллить сначала грубо, а потом "докручивать" до правильной позиции.
« Последнее редактирование: 16-05-2013 10:58 от Вад » Записан
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #14 : 16-05-2013 18:59 » 

Вад, не думаю, что там есть, чему тормозить. Попробуй, недолго же. Но вот на строки "мысленно" разбить, возможно, придётся при инициализации, чтобы на лету это не делать. А посадить строку в ячейку с браузерком - это быстро (модель же обрабатывает только видимые в данный момент ячейки)
« Последнее редактирование: 16-05-2013 19:01 от Алексей++ » Записан

Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines