Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: gil9red от Август 12, 2012, 17:32



Название: QTextEdit + Изменение регистра символов
Отправлено: gil9red от Август 12, 2012, 17:32
Здраствуйте!
Кому не лень, помогите :)
делаю панельку форматирования текста, связанную с textEdit, в качестве основы,
взял пример Text Edit
Захотелось добавить возможность поменять регистр выделенных символов, для этого использую функции:
Код:
QString UTextEdit::makeCaseSensitiveUpper(QString InputString)
{
    return InputString.toUpper();
}
QString UTextEdit::makeCaseSensitiveLower(QString InputString)
{
    return InputString.toLower();
}
QString UTextEdit::makeCaseSensitiveNegative(QString InputString)
{
    for (int i = 0; i < InputString.size(); i++)
    {
        if(InputString[i].isLower())
            InputString[i] = InputString[i].toUpper();
        else
            InputString[i] = InputString[i].toLower();
    }
    return InputString;
}

для изменения выделенных символов делаю следующее:
Код:
enum UTypesOfCaseSensitive
{
    UpperCaseSensitive,
    LowerCaseSensitive,
    NegativeCaseSensitive
};

void UTextEdit::textCaseSensitiveEdit(UTypesOfCaseSensitive typeOfCaseSensitive)
{
    QTextCursor cursor = this->textCursor();

    if(!cursor.selectedText().isEmpty())
    {
        cursor.beginEditBlock();

        if(typeOfCaseSensitive == UpperCaseSensitive)
        {
            QTextDocumentFragment textFragment = cursor.selection();
            QString text          = textFragment.toPlainText();
            QString changeText = makeCaseSensitiveUpper(text);
            this->setText(this->toPlainText().replace(QString(text),
                                                 QString(changeText));

        }else if(typeOfCaseSensitive == LowerCaseSensitive)
        {
            QTextDocumentFragment textFragment = cursor.selection();
            QString text          = textFragment.toPlainText();
            QString changeText = makeCaseSensitiveLower(text);
            this->setText(this->toHtml().replace(QString(text),
                                                 QString(changeText));
        }else if(typeOfCaseSensitive == NegativeCaseSensitive)
        {
            QTextDocumentFragment textFragment = cursor.selection();
            QString text          = textFragment.toPlainText();
            QString changeText = makeCaseSensitiveNegative(text);
            this->setText(this->toHtml().replace(QString(text),
                                                 QString(changeText));
        }

        cursor.endEditBlock();
     }
}

Вот до этого смог додуматься, но есть проблемы:
Код:
this->setText(this->toHtml().replace(QString(text),
                                                 QString(changeText));
    • такой код не годится - будет весело, если в тексте будет что то повторяться
    • при таком коде, если среди выделенных символов есть отличающиеся шрифтом, то замены не будет

    можно замену делать вот так:
    [/list]
    Код:
    this->setText(this->toPlainText().replace(QString(text),
                                                     QString(changeText));
    тогда:
    • после замены, если текст располагался на нескольких строках, все они окажутся на первой
    • форматирование текста (цвет, выделение, подчеркивания) пропадает

    Можно вместо QString & QString::replace ( const QString & before, const QString & after, Qt::CaseSensitivity cs = Qt::CaseSensitive ),
    использовать QString & QString::replace ( int position, int n, const QString & after ),
    тогда проблема в том что указание позиции курсора текста, в качестве параметра не катит,
    ведь мне нужно чтобы была поддержка rich text, а это html текст, а курсор в тексте не подходит для использования в html тексте, т.е. что то вроде этого не прокатит:
    Код:
                QTextDocumentFragment textFragment = cursor.selection();
                QString text          = textFragment.toPlainText();
                QString changeText = makeCaseSensitiveNegative(text);
                this->setText(this->toHtml().replace(cursor.position(),
                                                     text.size(),
                                                     changeText);

    Помогите с написанием кода, который бы заменял выделенный текст в TextEdit, на тот который с измененным регистром, и при этом изменяемый текст сохранял бы свой шрифт:
    до: Hello World! после: HELLO WORLD!
    Спасибо :)


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: Swa от Август 13, 2012, 10:14
    Во-первых:
    Код:
    QTextDocumentFragment textFragment = cursor.selection();
    QString text          = textFragment.toPlainText();
    - для этого есть метод QString QTextCursor::selectedText () const

    Во-вторых: Зачем это?
    Код:
    this->setText(this->toPlainText().replace(QString(text),QString(changeText));
    Вы уже нашли валидный курсор с выделенным текстом, а затем выполняете поиск по всему документу. Чтобы изменить выделенный текст используйте
    void QTextCursor::insertText ( const QString & text )
    Он заменит выделенный текст на text.


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 10:52
    Я выбрал cursor.selection() потому что в документации было написано что это в отличии от selectedText() используется для работы с rich text, потом использовал textFragment.toHTML(), но оно не прокатило, поэтому перешел на toPlainText().

    Сделал с insertText(), оно работает, только не годится оно для rich text - если например использую следующий код:
    Код:
                QString text       = cursor.selectedText();
                QString changeText = makeCaseSensitiveUpper(text);
                cursor.insertText(changeText);
    только есть проблемка с QTextCharFormat: cursor.insertText(), использует тот QTextCharFormat, который находится перед позицией курсора, например:
    до: helloo выделяем "llo", теперь в зависимости от того с какого края выделяли: слева-направо или наоборот будет разный результат:
    слева-направо: llo
    справо-налево: llo

    Ну как то так=)


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 11:11
    вот если бы можно было получить QTextCharFormat для каждого символа выделенного текста и скажем сохранить их например в контейнер, то:
    Код:
                QString text       = cursor.selectedText();
                QString changeText = makeCaseSensitiveUpper(text);
                for(int i = 0; i < changeText.size(); i++)
                    cursor.insertText(changeText.data()[i]);
    и в функции insertText(), в качестве второго параметра передавать QTextCharFormat данного символа, то что это сработает показывает код снизу:
    Код:
       
                QTextCharFormat charFormat;
                charFormat.setForeground(Qt::yellow);
                cursor.insertText(changeText.data()[0], charFormat);


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: Swa от Август 13, 2012, 12:09
    Вот держите:
    Код:
    QTextCursor cursor = this->textCursor();
    int start = cursor.selectionStart();
    int end = cursor.selectionEnd();
    int cStart = 0;
    int cEnd = 0;
    QTextBlock block = document()->findBlock(start);

    while (true) {
    if (!block.isValid()) {break;}
    if (block.position() > end) {break;}
    QTextBlock::iterator it;
    for(it = block.begin(); !(it.atEnd()); ++it) {
    QTextFragment currentFragment = it.fragment();
    if (!currentFragment.isValid()) {continue;}
    int fs = currentFragment.position();
    int fe = currentFragment.position() + currentFragment.length();

    if (end < fs || start > fe) {continue;}

    QTextCursor temp(document());

    if (start < fs) {
    cStart = fs;
    } else {
    cStart = start;
    }

    if (end < fe) {
    cEnd = end;
    } else {
    cEnd = fe;
    }

    temp.setPosition(cStart);
    temp.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, cEnd - cStart);
    QString text = temp.selectedText();
    text = text.toUpper();
    temp.beginEditBlock();
        temp.insertText(text);
    temp.endEditBlock();
    }
    block = block.next();
    }


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 13:47
    даааа...вот так работать с text edit и курсором я пока не умею... :(
    Спасибо, Swa за рабочий пример, буду изучать :)


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 14:50
    Кое что конечно не понятно: :)

    Код:
    QTextCursor cursor = this->textCursor();

    int start = cursor.selectionStart();
    int end = cursor.selectionEnd();     
    int cStart = 0;
    int cEnd = 0;
    // получем текстовый блок используя позицию курсора
    QTextBlock block = document()->findBlock(start);

    while (true) {
    if (!block.isValid()) {break;} // если блок не существует
    if (block.position() > end) {break;} // если позиция первого символа в блоке > end (а это может быть??)
    QTextBlock::iterator it;

    // перебираем символы блока с начала до конца
    for(it = block.begin(); !(it.atEnd()); ++it) {
    // текстовый фрагмент - это ведь слова, символы, отделенные друг от друга разделителем (пробел например)?
    QTextFragment currentFragment = it.fragment();
    if (!currentFragment.isValid()) {continue;} // если не существует
    int fs = currentFragment.position(); // нач позиция
    int fe = currentFragment.position() + currentFragment.length(); // конечная

    if (end < fs || start > fe) {continue;} // еще одна проверка

    QTextCursor temp(document()); // а почему получаем именно от документа курсор, а не от text edit?

                          // определяем нач и кон позицию
                          // передвижения курсора по документу
    if (start < fs) {
    cStart = fs;
    } else {
    cStart = start;
    }

    if (end < fe) {
    cEnd = end;
    } else {
    cEnd = fe;
    }

    // курсор будет находиться в позиции cStart
                          temp.setPosition(cStart);
                          // сдвигаем вправо, выделяя пройденные символы
                          temp.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, cEnd - cStart);

    QString text = temp.selectedText();

    text = text.toUpper();

                           //помещаем вставку в этот блок, чтобы можно было использовать после
    //undo и redo
                          temp.beginEditBlock();
        temp.insertText(text);
    temp.endEditBlock();
    }
                // смотрим следующий блок
    block = block.next();
    }


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: Bepec от Август 13, 2012, 14:52
    QTextEdit - это представление. Данные/цвет/позиции курсора/шрифт, всё это хранится в QDocument - модели.


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 15:01
    А можно будет облегчить код, не используя блоки текста и фрагменты, а получая курсор от документа, и у выделенных курсором символов поменять регистр, при этом не потеряв форматирования символов (шрифт, выделение, почеркивание, цвет)?


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: Swa от Август 13, 2012, 15:19
    Насколько я понимаю, это - минимальный код для этой задачи.
    Тут дело немного сложнее, чем кажется. Если вы повнимательнее почитаете документацию, то узнаете, что формат текста хранится в QTextFragment, то есть фрагмент - это кусок текста с одинаковым форматированием. Формат не хранится для каждого символа. Например, в тексте "раз два три" находятся три фрагмента. Чтобы изменить текст фрагмента, не потеряв его форматирования, нужно установить курсор внутри фрагмента.


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 13, 2012, 15:25
    Swa, спасибо за такой ясный и понятный ответ :)
    все, вопросов нет :)


    Название: Re: QTextEdit + Изменение регистра символов
    Отправлено: gil9red от Август 14, 2012, 13:47
    Наткнулся на ошибку :(
    для моего случая, изменил добавил немного код:
    Код:
    void UTextEdit::textCaseSensitiveEdit(UTypesOfCaseSensitive typeOfCaseSensitive)
    {
        QTextCursor cursor = this->textCursor();

        int startPosition = cursor.selectionStart();
        int endPosition   = cursor.selectionEnd();
        int cursorStart   = 0;
        int cursorEnd     = 0;

        QTextBlock block = document()->findBlock(startPosition);

        while(true)
        {
            if(!block.isValid())
                break;

            if(block.position() > endPosition)
                break;

            QTextBlock::iterator it;

            for(it = block.begin(); !(it.atEnd()); ++it)
            {
                QTextFragment currentFragment = it.fragment();
                if (!currentFragment.isValid())
                    continue;

                int fragmentLength = currentFragment.length();

                int fragmentStart  = currentFragment.position();
                int fragmentEnd    = fragmentStart + fragmentLength;

                if (endPosition < fragmentStart || startPosition > fragmentEnd)
                    continue;

                QTextCursor temp(document());

                if(startPosition < fragmentStart)
                    cursorStart = fragmentStart;
                else
                    cursorStart = startPosition;

                if(endPosition < fragmentEnd)
                    cursorEnd = endPosition;
                else
                    cursorEnd = fragmentEnd;

                temp.setPosition(cursorStart);
                temp.movePosition(QTextCursor::Right,
                                  QTextCursor::KeepAnchor,
                                  cursorEnd - cursorStart);
                QString text = temp.selectedText();

                if(typeOfCaseSensitive == UpperCaseSensitive)
                {
                    text = makeCaseSensitiveUpper(text);
                }else if(typeOfCaseSensitive == LowerCaseSensitive)
                {
                    text = makeCaseSensitiveLower(text);
                }else if(typeOfCaseSensitive == NegativeCaseSensitive)
                {
                    text = makeCaseSensitiveNegative(text);
                }

                temp.beginEditBlock();
                temp.insertText(text);
                temp.endEditBlock();
            }
            block = block.next();
        }
    }

    Если при выделении текста началом выделения будет первый символ абзаца, то при повторном использовании функции программа падает, посмотрел в отладке, и он указывает на строку с циклом for, и похоже проблема именно в ++it
    Помогите исправить это :)