Разработка кроссплатформенных приложений с использованием Juce

Шрифты и строки

Разбить на страницы
Показывать лекцию целиком

Цель лекции: Дать базовые представления о работе с текстом в Juce

Основным способом "общения" компьютерной программы с пользователем является вывод текстовых сообщений (диалоговые окна, надписи на ярлыках, панелях и т.п.). Внешний вид таких сообщений задаётся тем или иным шрифтом.

Масштабируемый шрифт — это рисунок, задающий внешний вид текста и позволяющий без искажений отображать его на экране компьютера и при печати. Функции отображения и печати шрифта берут на себя специальные функции растеризации, которые преобразуют математическое представление шрифта в растровую матрицу.

Для работы со шрифтами в Juce существует специальный класс, Font. Мы уже использовали его в предыдущих лекциях. Объект этого класса передаётся в качестве параметра в метод setFont() различных компонентов (Label, TextEditor, HyperlinkButton и др.). Класс Component метода setFont() не имеет.

Основные свойства компьютерных шрифтов заимствованы из типографского дела. Ими являются следующие:

  • Typeface — гарнитура или внешний вид шрифта, определяемый набором символов, имеющих стилистическое единство. Возвращается методом Typeface* Font::getTypeface() const. Если нужно получить не сам объект гарнитуры, а только её название для текущего шрифта (строку), то можно воспользоваться методом const String Font::getTypefaceName() const throw().
  • Высота шрифта в пикселях. Возвращается методом float Font::getHeight() const throw().
  • Стиль шрифта: нормальный, полужирный, курсив и подчёркнутый. Класс Font включает нумерованный список enum FontStyleFlags {plain = 0, bold = 1, italic = 2, underlined = 4}, элементы которого соответствуют вышеперечисленным стилям.
  • Если программа Juce не может найти шрифт с заданной комбинацией свойств, то используется другой шрифт, далеко не всегда близкий по свойствам. Поэтому можно воспользоваться методами класса Font для получения названий гарнитур шрифтов, использующихся по умолчанию в данной операционной системе:
  • static const String Font::getDefaultMonospacedFontName() — возвращает имя гарнитуры моноширинного рубленного шрифта, используемого по умолчанию;
  • static const String Font::getDefaultSerifFontName() — возвращает имя гарнитуры шрифта с засечками (Serif), используемого по умолчанию;
  • static const String Font::getDefaultSansSerifFontName() — возвращает имя гарнитуры рубленного шрифта (Sans), используемого по умолчанию.
  • По умолчанию для всех компонентов Juce задаётся нормальный (стиль plain) шрифт гарнитуры Sans размером 15 пикселей.

    В большинстве текстовых редакторов задаётся кегль шрифта — размер высоты символов, измеряемый в типографских пунктах. В Juce высота шрифта, задаваемая методом void Font::setHeight(float newHeight), как упоминалось, измеряется в пикселях. Получить её, исходя из заданного пользователем кегля в типографских пунктах, можно по формуле:

    Высота (пиксели) = Кегль (пункты) ? DPI / 72,
    

    где DPI (dots per inch) — число пикселей на дюйм, зависящее от характеристик монитора.

    Рассмотрим работу со шрифтами на примере демонстрационного приложения (рис 6.1).

    (рис 6.1) Демонстрационное приложение свойств шрифта

    В приложении текст из поля ввода ().

    #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //-------------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //-------------------------------------------------------
    // Класс компонента содержимого.
    // Наследует классы слушателей выпадающих списков и кнопок
    class TCentralComponent  : public Component,
              public ComboBoxListener,
              public ButtonListener
    {
    public:
      TCentralComponent();
      ~TCentralComponent();
    
      void paint(Graphics);
      void resized();
      // Функция, отслеживающая изменения в выпадающих списках
      void comboBoxChanged(ComboBox*);
      // Функция, отслеживающая щелчки по кнопке
      void buttonClicked(Button*);
    
    private:
      // Текущий шрифт текста, отображаемого в TextEditor
      Font CurrentFont;
      
      // Многострочное поле ввода
      TextEditor* pFontViewer;
      Label* pFontNameLabel;
      // Выбор гарнитуры шрифта
      ComboBox* pFontsBox;
      Label* pFontSizeLabel;
      // Выбор размера (кегля) шрифта
      ComboBox* pFontSizeBox;
      Label* pFontStyleLabel;
      GroupComponent* pFontGroup;
      // Радиокнопка выбора стиля нормального шрифта
      ToggleButton* pNormalButton;
      // Радиокнопка выбора стиля полужирного шрифта
      ToggleButton* pBoldButton;
      // Радиокнопка выбора стиля курсива
      ToggleButton* pItalicButton;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //----------------------------------------------------
    #endif
    

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

    #include "TCentralComponent.h"
    //--------------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //--------------------------------------------------------
    TCentralComponent::TCentralComponent() : Component("Central Component"),
               pFontViewer(0),
               pFontNameLabel(0),
               pFontsBox(0),
               pFontSizeLabel(0),
               pFontSizeBox(0),
               pFontStyleLabel(0),
               pFontGroup(0),
               pNormalButton(0),
               pBoldButton(0),
               pItalicButton(0)
    {
      pFontViewer = new TextEditor("FontViewer");
      // Поле ввода - многострочное
      pFontViewer->setMultiLine(true);
      // Новая строка при нажатии клавиши <ENTER>
      pFontViewer->setReturnKeyStartsNewLine(true);
      // Запрет на редактирование
      pFontViewer->setReadOnly(true);
      // Показать полосы прокрутки
      pFontViewer->setScrollbarsShown(true);
      pFontViewer->setText(tr("Съешь же ещё этих мягких французских булок да выпей чаю.\n1234567890\n`~@#$%^*()-_=+\\|{[]};:'\"/?"));
      addAndMakeVisible(pFontViewer);
    
      pFontNameLabel = new Label("FontNameLabel", tr("Гарнитура (вид) шрифта"));
      pFontNameLabel->setFont(Font(15.0000f, Font::plain));
      pFontNameLabel->setJustificationType(Justification::centredLeft);
      pFontNameLabel->setEditable(false, false, false);
      pFontNameLabel->setColour(Label::textColourId, Colours::black);
      pFontNameLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontNameLabel);
      
      pFontsBox = new ComboBox("FontsBox");
      // Запрет на редактирование текста в выпадающем списке
      pFontsBox->setEditableText(false);
      // Выравнивание теста по левому краю и центру виджета
      pFontsBox->setJustificationType(Justification::centredLeft);
      pFontsBox->setTextWhenNothingSelected(tr("Стандартный шрифт"));
      // Устанавливаем в качестве слушателя списка
      // сам компонент-контейнер
      pFontsBox->addListener (this);
      addAndMakeVisible(pFontsBox);
      
      // Получаем имена системных шрифтов, сохраняем их 
      // в строковом массиве...
      StringArray sSystemFonts = Font::findAllTypefaceNames();
      // и добавляем в выпадающий список
      for(int i = 0; i < sSystemFonts.size(); i++)
      {
        pFontsBox->addItem(sSystemFonts[i], i + 1);
      }
    
      pFontSizeLabel = new Label("FontSizeLabel", tr("Размер (кегль) шрифта"));
      pFontSizeLabel->setFont(Font(15.0000f, Font::plain));
      pFontSizeLabel->setJustificationType(Justification::centredLeft);
      pFontSizeLabel->setEditable(false, false, false);
      pFontSizeLabel->setColour(Label::textColourId, Colours::black);
      pFontSizeLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontSizeLabel);
      
      pFontSizeBox = new ComboBox("FontSizeBox");
      pFontSizeBox->setEditableText(false);
      pFontSizeBox->setJustificationType(Justification::centredLeft);
      pFontSizeBox->setTextWhenNothingSelected(tr("Размер по умолчанию"));
      pFontSizeBox->addItem("8", 1);
      pFontSizeBox->addItem("9", 2);
      pFontSizeBox->addItem("10", 3);
      pFontSizeBox->addItem("11", 4);
      pFontSizeBox->addItem("12", 5);
      pFontSizeBox->addItem("13", 6);
      pFontSizeBox->addItem("14", 7);
      pFontSizeBox->addItem("15", 8);
      pFontSizeBox->addItem("16", 9);
      pFontSizeBox->addItem("17", 10);
      pFontSizeBox->addItem("18", 11);
      pFontSizeBox->addItem("19", 12);
      pFontSizeBox->addItem("20", 13);
      // Устанавливаем в качестве слушателя списка
      // сам компонент-контейнер
      pFontSizeBox->addListener (this);
      addAndMakeVisible(pFontSizeBox);
      
      pFontStyleLabel = new Label("FontStyleLabel", tr("Стиль шрифта"));
      pFontStyleLabel->setFont(Font(15.0000f, Font::plain));
      pFontStyleLabel->setJustificationType(Justification::centredLeft);
      pFontStyleLabel->setEditable(false, false, false);
      pFontStyleLabel->setColour(Label::textColourId, Colours::black);
      pFontStyleLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontStyleLabel);
      
      pFontGroup = new GroupComponent("FontGroup", tr("Шрифт"));
      pFontGroup->setTextLabelPosition(Justification::centredLeft);
      addAndMakeVisible(pFontGroup);
      
      pNormalButton = new ToggleButton("NormalButton");
      pNormalButton->setButtonText(tr("Нормальный"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pNormalButton->addListener(this);
      // Радиокнопка отмечена
      pNormalButton->setToggleState(true, false);
      pNormalButton->setRadioGroupId(1234);
      addAndMakeVisible(pNormalButton);
      
      pBoldButton = new ToggleButton("BoldButton");
      pBoldButton->setButtonText(tr("Полужирный"));
      pBoldButton->addListener(this);
      // Радиокнопка не отмечена
      pBoldButton->setToggleState(false, false);
      pBoldButton->setRadioGroupId(1234);
      addAndMakeVisible(pBoldButton);
      
      pItalicButton = new ToggleButton("ItalicButton");
      pItalicButton->setButtonText(tr("Курсив"));
      pItalicButton->addListener(this);
      pItalicButton->setToggleState(false, false);
      pItalicButton->setRadioGroupId(1234);
      addAndMakeVisible(pItalicButton);
    
      setSize (600, 400);
    }
    //------------------------------------------------------
    

    Названия гарнитур системных шрифтов мы получаем с помощью метода static const StringArray Font::findAllTypefaceNames(), который возвращает массив строк (класс StringArray Juce), содержащий эти названия. Число элементов массива возвращается его методом int StringArray::size() const throw(). В цикле

    for(int i = 0; i < sSystemFonts.size(); i++)
    {
        pFontsBox->addItem(sSystemFonts[i], i + 1);
    } 
    

    мы перебираем по одному элементы списка и добавляем их в качестве элементов выпадающего списка pFontsBox.

    Элементы списка размера шрифтов(pFontSizeBox) добавляем в диапазоне от 8 до 20 пунктов.

    Заметим, что в Juce групповой блок служит исключительно в качестве элемента оформления; радиокнопки, рисуемые на его поверхности, не являются его потомками, а представляют собой самостоятельные виджеты. Для того, чтобы объединить их в одну группу, необходимо воспользоваться методом void Button::setRadioGroupId(int newGroupId). Если метод у нескольких радиокнопок принимает в качестве параметра какое-либо одно, отличное от нуля, число, то эти кнопки начинают действовать как единая радиогруппа, т.е. возможно включение одной и только одной кнопки из набора. В качестве ID для нашей радиогруппы используется число 1234.

    Деструктор и методы перерисовки и задания размеров нашего класса приведены в листинге 6.3 .

    TCentralComponent::~TCentralComponent()
    {
      // Удаляем дочерние виджеты
      // и обнуляем их указатели
      deleteAllChildren();
    }
    //-------------------------------------------------
    void TCentralComponent::paint(Graphics Canvas)
    {
      Canvas.fillAll(Colours::azure);
    }
    //-------------------------------------------------
    void TCentralComponent::resized()
    {
      pFontViewer->setBounds(proportionOfWidth(0.5505f), 8,    
                proportionOfWidth(0.4192f), 
                proportionOfHeight(0.9507f));
      pFontNameLabel->setBounds(8, 8,       
                roundFloatToInt((proportionOfWidth(0.4192f)) *  
                1.2048f), 25);
      pFontsBox->setBounds(8, 38, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontSizeLabel->setBounds (8, 68, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontSizeBox->setBounds(8, 96, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontStyleLabel->setBounds(8, 126, 
                 roundFloatToInt((proportionOfWidth(0.4192f)) * 
                 1.2048f), 25);
      pFontGroup->setBounds(8, 156, 
                 roundFloatToInt((proportionOfWidth(0.4192f)) * 
                 1.2048f), 196);
      pNormalButton->setBounds(32, 185, 150, 25);
      pBoldButton->setBounds(32, 216, 150, 25);
      pItalicButton->setBounds(32, 248, 150, 25);
    }
    

    Для задания относительных горизонтальных размеров виджетов мы использовали уже знакомую функцию int Component::proportionOfWidth(float proportion) const throw(). Для преобразования чисел с плавающей точкой, получаемых после расчёта координат компонентов, в целые использована функция из juce_MathsFunctions.h int roundFloatToInt(const float value) throw().

    Как видно из рисунка 6.1 , гарнитура и размер шрифта текста, отображаемого в многострочном текстовом поле TextEditor* pFontViewer, выбираются пользователем из выпадающих списков. Поскольку сам компонент-контейнер TCentralComponent является слушателем изменений списков (ComboBoxListener), в его классе мы переопределяем наследуемую от класса linstener'а виртуальную функцию virtual void ComboBox::Listener::ComboBoxChanged(ComboBox* ComboNoxThatHasChanged), которая отвечает за реакцию программы на выбор пользователя. Её реализация приведена в листинге 6.4 .

    void TCentralComponent::comboBoxChanged(ComboBox* pComboBox)
    {
      // Изменён выпадающий список гарнитуры шрифта
      if(pComboBox == pFontsBox)
      {
        // Устанавливаем в качестве шрифта текста поля ввода
        // шрифт, выбранный пользователем из списка
        CurrentFont.setTypefaceName(pFontsBox->getText());
      }
      // Изменён выпадающий список размера шрифта
      else if(pComboBox == pFontSizeBox)
      {
        // Вычисляем размер шрифта в пикселях,
        // исходя из выбранного пользователем кегля в типографских пунктах
        float fFontHeight = pFontSizeBox->getText().getFloatValue() * 96 
        / 72;
        // Устанавливаем размер шрифта
        CurrentFont.setHeight(fFontHeight);
      }
      // Задаём шрифт для отображения текста
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

    После того, как пользователь выбрал какой-либо пункт в выпадающем списке, отображаемый текст виджета изменяется соответственно надписи выбранного пункта. Получить этот текст можно с помощью функции const String ComboBox::getText() const. В случае первого списка (pFontsBox) это будет название выбранной пользователем гарнитуры шрифта.

    Это название передаём в качестве параметра в функию void void Font::setTypefaceName(const String faceName). Как понятно из названия, она устанавливает гарнитуру вызвавшего шрифта (объект класса Font) с названием, соответствующим строке-параметру. Если гарнитура с таким названием не будет найдена (шрифт не установлен на компьютере), то будет использоваться шрифт по умолчанию. В нашем случае такого происходить не должно, т.к. пользователь выбирает из ранее найденных названий шрифтов, установленных в системе.

    Во втором выпадающем списке,

    Теперь можно использовать шрифт с заданными пользователем параметрами для отображения текста в многострочном поле ввода pFontViewer. У класса TextEditor есть для этого два метода:

  • void TextEditor::setFont(const Font newFont), который устанавливает шрифт для вновь добавляемого в поле текста;
  • void TextEditor::applyFontToAllText(const Font newFont), который применяет параметры шрифта ко всему тексту поля ввода.
  • Понятно, что в нашем случае подходит именно второй метод ().

    Задать стиль текста в нашей демонстрационной программе пользователь может посредством трёх радиокнопок, которые мы отнесли к одной группе с ID 1234. Это обеспечивает выбор одной и только одной радиокнопки.

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

  • void Font::setBold(bool shouldBeBold) — задаёт полужирное начертание шрифта;
  • void Font::setItalic(bool shouldBeItalic) — задаёт курсивное начертание шрифта;
  • void Font::setUnderline(bool shouldBeUnderline) — задаёт подчёркнутое начертание шрифта.
  • Значение принимаемого параметра, равное true, задаёт соответствующий стиль шрифта, а равное false — его отменяет. Специальной функции для задания нормального стиля шрифта в Juce нет; для того, чтобы шрифт отображался нормальным начертанием, необходимо отменить все остальные стили с помощью вышеприведённых функций.

    Для того, чтобы узнать, какой стиль используется шрифтом, в классе Font библиотеки Juce есть следующие методы:

  • bool Font::isBold() const throw();
  • bool Font::isItalic() const throw();
  • bool Font::isUnderlined() const throw().
  • В том случае, если функция возвращает истину, начертание шрифта является полужирным, курсивным или подчёркнутым, соответственно.

    Попробуем применить эти сведения в нашей программе (см. первый вариант реализации функции ).

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      if (pButton == pNormalButton)
      {
        if(CurrentFont.isBold())
        {
          CurrentFont.setBold(false);
        }
        else if(CurrentFont.isItalic())
        {
          CurrentFont.setItalic(false);  
        }
      }
      else if (pButton == pBoldButton)
      {
        if(CurrentFont.isItalic())
        {
          CurrentFont.setItalic(false);
        }
        CurrentFont.setBold(true);
      }
      else if (pButton == pItalicButton)
      {
        if(CurrentFont.isBold())
        {
          CurrentFont.setBold(false);
        }
        CurrentFont.setItalic(true);
      }
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

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

    CurrentFont.setBold(true);
    CurrentFont.setItalic(true);
    

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

    Для того, чтобы задать один и только один стиль шрифта, нужно воспользоваться флагами стиля (упоминавшийся выше перечислимый тип FontStyleFlags. Они принимаются в качестве параметра функцией void Font::setStyleFlags(int newFlags), которая меняет стиль вызвавшего шрифта. Комбинация стилей в этой функции задаётся операцией побитового сложения (|).

    Перепишем реализацию функции ). Код стал значительно компактнее при сохранении функциональности программы.

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      if (pButton == pNormalButton)
      {
        // Задаём стиль шрифта - нормальный
        CurrentFont.setStyleFlags(Font::plain);
      }
      else if (pButton == pBoldButton)
      {
        // Задаём стиль шрифта - полужирный
        CurrentFont.setStyleFlags(Font::bold);
      }
      else if (pButton == pItalicButton)
      {   
        // Задаём стиль шрифта - курсив
        CurrentFont.setStyleFlags(Font::italic);
      }
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

    Для получения флага стиля того или иного шрифта используется функция int Font::getStyleFlags() const throw().

    Завершая описание класса Font библиотеки Juce, следует отметить, что для него доступны операторы присваивания = (копирует один шрифт в другой) и сравнения (== и !=).

    Строки в Juce

    Подобно многим другим GUI toolkit'ам в Juce представление строковых данных реализовано как класс (String). Мы уже встречались с этим классом в вышеприведённых программах.

    Класс String имеет несколько конструкторов:

  • String() throw() — создаёт пустую строку;
  • String(const String other) throw() — создаёт строку из другого объекта того же класса String;
  • String(const char* text) — создаёт строку из массива символов, оканчивающегося нулём;
  • String(const char* text, size_t maxChars) — создаёт строку из массива символов определённой длины;
  • String(const juce_wchar* unicodeText) — создаёт строку из массива символов в формате Юникод, оканчивающегося нулём; typedef wchar_t juce_wchar — платформонезависимый тип символа Юникод;
  • String(const juce_wchar* unicodeText, size_t maxChars) — создаёт строку из массива символов в формате Юникод определённой длины.
  • Класс String предоставляет целую серию методов и операторов, позволяющих проводить со строками разного рода операции: соединять их, осуществлять поиск подстрок и т.п.

    Для класса String имеется два перегруженных оператора: присваивания (=) и конкатенации (+=).

    Оператор = замещает содержимое объекта класса String содержимым другой строки.

    Оператор += осуществляет объединение или конкатенацию строк. При этом в конец исходной строки могут быть добавлены данные следующих типов:

  • символ (char);
  • символ Юникод (juce_wchar);
  • массив символов Юникод (juce_wchar*);
  • строка (String);
  • целое число (int);
  • беззнаковое целое (unsigned int).
  • Объединение строк можно осуществить также с помощью функции-члена класса String void String::append(const juce_wchar* textToAppend, int maxCharsToTake), где textToAppend — строка, добавляемая в конец исходной, а maxCharsToTake — максимальное число передаваемых символов.

    Строки можно сравнивать между собой с помощью набора методов класса:

  • bool String::equalsIgnoreCase(const String other) const throw() — сравнение с другой строкой без учёта регистра;
  • bool String::equalsIgnoreCase(const juce_wchar* other) const throw() — сравнение с другой строкой (представленной массивом символов Юникод) без учёта регистра;
  • bool String::equalsIgnoreCase(const char* other) const throw() — сравнение с другой строкой (представленной массивом символов) без учёта регистра;
  • int String::compare(const String other) const throw() — сравнение с другой строкой с учётом регистра;
  • int String::compare(const char* other) const throw() — сравнение с другой строкой (представленной массивом символов) с учётом регистра;
  • int String::compare(const juce_wchar* other) const throw() — сравнение с другой строкой (представленной массивом символов Юникод) с учётом регистра;
  • int String::compareIgnoreCase(const String other) const throw() — сравнение с другой строкой без учёта регистра;
  • int String::compareLexicographically(const String other) const throw() — лексикографическое сравнение с другой строкой.
  • Узнать, является ли строка пустой, можно с помощью следующих методов:
  • bool String::isEmpty() const throw() — возвращает true, если строка не содержит ни одного символа;
  • bool String::isNotEmpty() const throw() — возвращает true, если строка содержит хотя бы один символ.
  • Того же результата можно добиться, проверив длину строки в символах методом int String::length() const throw().

    Следует помнить, что строка, содержащая пробелы, пустой не является, поэтому функция isEmpty, вызываемая ею, будет возвращать false. Как правило, пользователь воспринимает текстовое поле, в котором нет других символов, кроме пробелов, именно как пустое. Библиотека Juce включает удобную функцию bool String::containsNonWhitespaceChars() const throw(), которая возвращает true, если вызывающая строка содержит какие-либо символы, помимо пробелов.

    Поиск строк и символов в тексте — одна из наиболее часто возникающих задач при разработке приложений. Класс String содержит довольно много методов для её решения.

    Прежде всего, это методы поиска подстроки:

  • const String String::substring(int startIndex) const — возвращает часть вызывающей строки от символа startIndex и до её конца;
  • const String String::substring(int startIndex, int endIndex) const — возвращает часть вызывающей строки между символами startIndex и endIndex.
  • Для того, чтобы узнать, содержит ли строка подстроку или символы (в любой позиции), используются следующие функции:

  • bool String::contains(const String text) const throw() — проверяет, содержит ли строка подстроку text. Чувствительна к регистру. Если в качестве параметра была передана пустая строка, функция всё равно возвращает true.
  • bool String::containsIgnoreCase(const String text) const throw() — работает аналогично предыдущей функции, но не учитывает регистр символов.
  • bool String::containsChar(juce_wchar character) const throw() — проверяет, содержит ли строка символ, переданный в качестве параметра.
  • bool String::containsAnyOf(const String charactersItMightContain) const throw() — возвращает true, если в вызывающей строке есть какие-либо символы из набора, принимаемого в качестве параметра.
  • bool String::containsOnly(const String charactersItMightContain) const throw() — возвращает false, если в вызывающей строке отсутствует какой-либо из символов набора, принимаемого в качестве параметра.
  • bool String::containsWholeWord(const String wordToLookFor) const throw() — возвращает true, если вызывающая метод строка содержит слово wordToLookFor, окружённое символами, не являющимися буквами и цифрами.
  • Для замены определённой части строки другой класс String предоставляет метод const String String::replace(const String stringToReplace, const String stringToInsertInstead, bool ignoreCase = false) const, все вхождения подстроки stringToReplace строкой stringToInsertInstead с учётом или без учёта регистра в зависимости от принимаемого значения параметра ignoreCase.

    Для замены одного символа в строке можно воспользоваться функцией const String String::replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const. Кроме того, класс String включает метод, который позволяет заменить один набор символов другим: const String String::replaceCharacters(const String charactersToReplace, const String charactersToInsertInstead) const. Он возвращает строку, в которой каждый символ из первого набора (charactersToReplace) замещается соответствующим символом из второго набора (charactersToInsertInstead) в эквивалентной позиции. Понятно, что передаваемые в функцию две строки должны быть одинакового размера. Например, при вызове replaceCharacters("abc", "def") в результирующей строке "a" замещается на "d", "b"на "e" и т.п.

    Близким к предыдущей функции является метод класса String const String String::retainCharacters(const String charactersToRetain) const, который возвращает строку, сохраняющую только тот набор символов, содержавшихся в вызвавшей строке, который был передан в качестве параметра. Например, если объект класса String, содержащий строку "1122334455", обратится к методу retainCharacters("432"), то будет возвращена строка "223344".

    Для преобразования данных строки в верхний или нижний регистр используются методы const String String::toLowerCase() const и const String String::toUpperCase() const, которые возвращают, соответственно, вариант вызвавшей строки в нижнем и верхнем регистре.

    Достаточно часто встречается ситуация, когда необходимо конвертировать числовые значения в строковые и наоборот. В первом случае необходимо создать объект класса String, в качестве параметра конструктора которого передать конвертируемое число. Принимаемыми в качестве параметра конструктора типами могут быть short int, unsigned short int, int, unsigned int, int64, uint64, float и double. Например:

    String sNumber(1234); // Строка содержит текст "1234".
    

    Преобразование из строкового в числовое значение производится методами, содержащими в своём имени название типа:

  • int String::getIntValue() const throw();
  • int64 String::getLargeIntValue() const throw();
  • float String::getFloatValue() const throw();
  • double String::getDoubleValue() const throw();
  • int String::getHexValue32() const throw();
  • int64 String::getHexValue64() const throw();
  • Последние две функции возвращают шестнадцатеричное число соответствующего размера; прочие символы, если их содержит строка, игнорируются. Пример использования функции getFloatValue приведён в листинге 6.4 .

    Кроме того, класс String включает довольно интересный метод int String::getTrailingIntValue() const throw(), который возвращает целое число, находящееся в конце строки. Например:

    String sNumber("321 xyz654");
    // Функция возвращает 654
    int iNumber = sNumber.getTrailingIntValue()
    

    Список строк (класс StringArray)

    Для хранения массива строковых объектов в Juce имеется класс StringArray, который описывает список строк с расширенными возможностями манипулирования ими.

    Класс поддерживает следующие операции работы со строками:

    Доступ к строке по её индексу возможен с помощью оператора [], как к элементу обычного массива. Если индекс выходит за пределы массива, будет возвращена пустая строка. Кроме того, получить ссылку на одну из строк массива по её индексу позволяет метод класса String StringArray::getReference(int index) throw().

    Возвращение числа элементов списка (хранящихся строк) осуществляет метод int StringArray::size() const throw().

    Добавление новых строк в конец списка. Производится функцией void StringArray::add(const String stringToAdd). Кроме того, в конец списка можно добавить целый массив строк, другой объект класса StringArray, с помощью метода void StringArray::addArray(const StringArray other, int startIndex = 0, int numElementsToAdd = -1), где other — первый элемент добавляемого строкового массива, с которого необходимо начинать вставку, а numElementsToAdd — максимальное число строк из добавляемого массива для вставки. Если значение параметра меньше нуля, то добавляются все элементы.

    Вставка новых строк в произвольное место списка со смещением элементов массива. Функция void StringArray::insert(int index, const String stringToAdd) вставляет строку stringToAdd в массив на место, заданное индексом index, поднимая другие элементы. Если значение индекса меньше нуля или превышает размер массива, то строка добавляется в конец списка.

    Вставка новых строк в произвольное место списка с заменой элементов массива. Функция void StringArray::set(int index, const String newString) вставляет строку newString в массив на место, заданное индексом index. При этом прежняя строка замещается новой. Если значение индекса меньше нуля функция не делает ничего, а если оно превышает размер массива, то строка добавляется в конец списка.

    Поиск строк в массиве (по содержимому). Чаще бывает, что найти необходимую строку в массиве бывает нужно не по её индексу, а по тексту, в ней содержащемуся. В этом может помочь метод bool StringArray::contains(const String stringToLookFor, bool ignoreCase = false) const. Также, как и раньше, второй параметр — флаг, следует ли при поиске строки stringToLookFor учитывать регистр.

    Удаление строк. Удалить одну строку, находящуюся на позиции index, из списка можно с помощью функции void StringArray::remove(int index). Если индекс выходит за пределы массива, то функция ничего не делает. Поиск строки stringToRemove в массиве с последующим её удалением выполняет функция void StringArray::removeString(const String stringToRemove, bool ignoreCase = false); второй её параметр — флаг, показывающий, следует ли игнорировать регистр.

    Полезны также функции, позволяющие удалить все повторяющиеся строки, void StringArray::removeDuplicates(bool ignoreCase), и все пустые строки в массиве, void StringArray::removeEmptyStrings(bool removeWhiteSpaceStrings = true). Если последняя функция в качестве параметра принимает истину, то пустыми будут считаться также строки, заполненные только пробелами.

    Кроме того, класс StringArray позволяет удалять сразу ряд строк. Для этого используется метод void StringArray::removeRange(int startIndex, int numberToRemove), где startIndex — индекс первой строки из ряда, подлежащего удалению, а numberToRemove — число удаляемых элементов.

    Перемещение строк на новую позицию в массиве осуществляет метод void StringArray::move(int currentIndex, int newIndex) throw(). При вызове функции экземпляром класса StringArray происходит перемещение содержащейся в нём строки, находящейся на позиции currentIndex, на позицию newIndex.

    Сортировка элементов списка осуществляется функцией void StringArray::sort(bool ignoreCase); параметр указывает, следует ли учитывать регистр. Строки сортируются в алфавитном порядке.

    Очистка списка. Удаление всех строк из списка с обнулением его размера выполняет функция void StringArray::clear().

    Нахождение индекса строки осуществляет функция int StringArray::indexOf(const String stringToLookFor, bool ignoreCase = false, int startIndex = 0) const, где stringToLookFor — текст (строка) для поиска, ignoreCase— флаг, показывающий, следует ли игнорировать регистр, а startIndex — индекс элемента, с которого начинается поиск. Функция возвращает индекс найденной строки, а в случае, если ничего не было найдено, возвращается -1.

    Объединение строк массива в одну. Удобно для автоматической генерации текста, добавляемого, скажем, в многострочное поле ввода на основании собранных и сохранённых в массиве StringArray данных. Осуществляется функцией const String StringArray::joinIntoString(const String separatorString, int startIndex = 0, int numberOfElements = -1) const.

    Мы можем с помощью параметра separatorString задать строку-разделитель, которая будет разделять элементы массива в результирующей строке. Например, если массив StringArray включал строки "a", "b" и "c", то вызов метода joinIntoString(",") вернёт строку "a,b,c".

    startIndex, как нетрудно догадаться, индекс элемента массива, с которого следует начать объединение, а numberOfElements — число объединяемых строк. В том случае, если последний параметр принимает значение меньше нуля, то будут объединены все строки массива.

    Краткие итоги

    Для вывода неформатированного текста в Juce используется класс String, а форматированного — также и класс Font, хранящий информацию о параметрах шрифта текста.

    Класс String, инкапсулирует действия со строками. Объект класса String — это строка, состоящая из произвольного числа символов. Литерные константы типа String представляют собой последовательности символов, заключённые в двойные кавычки.

    Дополнительные материалы

    Архив с исходными текстами примера Вы можете скачать здесь

    Страницы:

    Цель лекции: Дать базовые представления о работе с текстом в Juce

    Основным способом "общения" компьютерной программы с пользователем является вывод текстовых сообщений (диалоговые окна, надписи на ярлыках, панелях и т.п.). Внешний вид таких сообщений задаётся тем или иным шрифтом.

    Масштабируемый шрифт — это рисунок, задающий внешний вид текста и позволяющий без искажений отображать его на экране компьютера и при печати. Функции отображения и печати шрифта берут на себя специальные функции растеризации, которые преобразуют математическое представление шрифта в растровую матрицу.

    Для работы со шрифтами в Juce существует специальный класс, Font. Мы уже использовали его в предыдущих лекциях. Объект этого класса передаётся в качестве параметра в метод setFont() различных компонентов (Label, TextEditor, HyperlinkButton и др.). Класс Component метода setFont() не имеет.

    Основные свойства компьютерных шрифтов заимствованы из типографского дела. Ими являются следующие:

  • Typeface — гарнитура или внешний вид шрифта, определяемый набором символов, имеющих стилистическое единство. Возвращается методом Typeface* Font::getTypeface() const. Если нужно получить не сам объект гарнитуры, а только её название для текущего шрифта (строку), то можно воспользоваться методом const String Font::getTypefaceName() const throw().
  • Высота шрифта в пикселях. Возвращается методом float Font::getHeight() const throw().
  • Стиль шрифта: нормальный, полужирный, курсив и подчёркнутый. Класс Font включает нумерованный список enum FontStyleFlags {plain = 0, bold = 1, italic = 2, underlined = 4}, элементы которого соответствуют вышеперечисленным стилям.
  • Если программа Juce не может найти шрифт с заданной комбинацией свойств, то используется другой шрифт, далеко не всегда близкий по свойствам. Поэтому можно воспользоваться методами класса Font для получения названий гарнитур шрифтов, использующихся по умолчанию в данной операционной системе:
  • static const String Font::getDefaultMonospacedFontName() — возвращает имя гарнитуры моноширинного рубленного шрифта, используемого по умолчанию;
  • static const String Font::getDefaultSerifFontName() — возвращает имя гарнитуры шрифта с засечками (Serif), используемого по умолчанию;
  • static const String Font::getDefaultSansSerifFontName() — возвращает имя гарнитуры рубленного шрифта (Sans), используемого по умолчанию.
  • По умолчанию для всех компонентов Juce задаётся нормальный (стиль plain) шрифт гарнитуры Sans размером 15 пикселей.

    В большинстве текстовых редакторов задаётся кегль шрифта — размер высоты символов, измеряемый в типографских пунктах. В Juce высота шрифта, задаваемая методом void Font::setHeight(float newHeight), как упоминалось, измеряется в пикселях. Получить её, исходя из заданного пользователем кегля в типографских пунктах, можно по формуле:

    Высота (пиксели) = Кегль (пункты) ? DPI / 72,
    

    где DPI (dots per inch) — число пикселей на дюйм, зависящее от характеристик монитора.

    Рассмотрим работу со шрифтами на примере демонстрационного приложения (рис 6.1).

    (рис 6.1) Демонстрационное приложение свойств шрифта

    В приложении текст из поля ввода ().

    #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //-------------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //-------------------------------------------------------
    // Класс компонента содержимого.
    // Наследует классы слушателей выпадающих списков и кнопок
    class TCentralComponent  : public Component,
              public ComboBoxListener,
              public ButtonListener
    {
    public:
      TCentralComponent();
      ~TCentralComponent();
    
      void paint(Graphics);
      void resized();
      // Функция, отслеживающая изменения в выпадающих списках
      void comboBoxChanged(ComboBox*);
      // Функция, отслеживающая щелчки по кнопке
      void buttonClicked(Button*);
    
    private:
      // Текущий шрифт текста, отображаемого в TextEditor
      Font CurrentFont;
      
      // Многострочное поле ввода
      TextEditor* pFontViewer;
      Label* pFontNameLabel;
      // Выбор гарнитуры шрифта
      ComboBox* pFontsBox;
      Label* pFontSizeLabel;
      // Выбор размера (кегля) шрифта
      ComboBox* pFontSizeBox;
      Label* pFontStyleLabel;
      GroupComponent* pFontGroup;
      // Радиокнопка выбора стиля нормального шрифта
      ToggleButton* pNormalButton;
      // Радиокнопка выбора стиля полужирного шрифта
      ToggleButton* pBoldButton;
      // Радиокнопка выбора стиля курсива
      ToggleButton* pItalicButton;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //----------------------------------------------------
    #endif
    

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

    #include "TCentralComponent.h"
    //--------------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //--------------------------------------------------------
    TCentralComponent::TCentralComponent() : Component("Central Component"),
               pFontViewer(0),
               pFontNameLabel(0),
               pFontsBox(0),
               pFontSizeLabel(0),
               pFontSizeBox(0),
               pFontStyleLabel(0),
               pFontGroup(0),
               pNormalButton(0),
               pBoldButton(0),
               pItalicButton(0)
    {
      pFontViewer = new TextEditor("FontViewer");
      // Поле ввода - многострочное
      pFontViewer->setMultiLine(true);
      // Новая строка при нажатии клавиши <ENTER>
      pFontViewer->setReturnKeyStartsNewLine(true);
      // Запрет на редактирование
      pFontViewer->setReadOnly(true);
      // Показать полосы прокрутки
      pFontViewer->setScrollbarsShown(true);
      pFontViewer->setText(tr("Съешь же ещё этих мягких французских булок да выпей чаю.\n1234567890\n`~@#$%^*()-_=+\\|{[]};:'\"/?"));
      addAndMakeVisible(pFontViewer);
    
      pFontNameLabel = new Label("FontNameLabel", tr("Гарнитура (вид) шрифта"));
      pFontNameLabel->setFont(Font(15.0000f, Font::plain));
      pFontNameLabel->setJustificationType(Justification::centredLeft);
      pFontNameLabel->setEditable(false, false, false);
      pFontNameLabel->setColour(Label::textColourId, Colours::black);
      pFontNameLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontNameLabel);
      
      pFontsBox = new ComboBox("FontsBox");
      // Запрет на редактирование текста в выпадающем списке
      pFontsBox->setEditableText(false);
      // Выравнивание теста по левому краю и центру виджета
      pFontsBox->setJustificationType(Justification::centredLeft);
      pFontsBox->setTextWhenNothingSelected(tr("Стандартный шрифт"));
      // Устанавливаем в качестве слушателя списка
      // сам компонент-контейнер
      pFontsBox->addListener (this);
      addAndMakeVisible(pFontsBox);
      
      // Получаем имена системных шрифтов, сохраняем их 
      // в строковом массиве...
      StringArray sSystemFonts = Font::findAllTypefaceNames();
      // и добавляем в выпадающий список
      for(int i = 0; i < sSystemFonts.size(); i++)
      {
        pFontsBox->addItem(sSystemFonts[i], i + 1);
      }
    
      pFontSizeLabel = new Label("FontSizeLabel", tr("Размер (кегль) шрифта"));
      pFontSizeLabel->setFont(Font(15.0000f, Font::plain));
      pFontSizeLabel->setJustificationType(Justification::centredLeft);
      pFontSizeLabel->setEditable(false, false, false);
      pFontSizeLabel->setColour(Label::textColourId, Colours::black);
      pFontSizeLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontSizeLabel);
      
      pFontSizeBox = new ComboBox("FontSizeBox");
      pFontSizeBox->setEditableText(false);
      pFontSizeBox->setJustificationType(Justification::centredLeft);
      pFontSizeBox->setTextWhenNothingSelected(tr("Размер по умолчанию"));
      pFontSizeBox->addItem("8", 1);
      pFontSizeBox->addItem("9", 2);
      pFontSizeBox->addItem("10", 3);
      pFontSizeBox->addItem("11", 4);
      pFontSizeBox->addItem("12", 5);
      pFontSizeBox->addItem("13", 6);
      pFontSizeBox->addItem("14", 7);
      pFontSizeBox->addItem("15", 8);
      pFontSizeBox->addItem("16", 9);
      pFontSizeBox->addItem("17", 10);
      pFontSizeBox->addItem("18", 11);
      pFontSizeBox->addItem("19", 12);
      pFontSizeBox->addItem("20", 13);
      // Устанавливаем в качестве слушателя списка
      // сам компонент-контейнер
      pFontSizeBox->addListener (this);
      addAndMakeVisible(pFontSizeBox);
      
      pFontStyleLabel = new Label("FontStyleLabel", tr("Стиль шрифта"));
      pFontStyleLabel->setFont(Font(15.0000f, Font::plain));
      pFontStyleLabel->setJustificationType(Justification::centredLeft);
      pFontStyleLabel->setEditable(false, false, false);
      pFontStyleLabel->setColour(Label::textColourId, Colours::black);
      pFontStyleLabel->setColour(Label::backgroundColourId,
              Colour(Colours::azure));
      addAndMakeVisible(pFontStyleLabel);
      
      pFontGroup = new GroupComponent("FontGroup", tr("Шрифт"));
      pFontGroup->setTextLabelPosition(Justification::centredLeft);
      addAndMakeVisible(pFontGroup);
      
      pNormalButton = new ToggleButton("NormalButton");
      pNormalButton->setButtonText(tr("Нормальный"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pNormalButton->addListener(this);
      // Радиокнопка отмечена
      pNormalButton->setToggleState(true, false);
      pNormalButton->setRadioGroupId(1234);
      addAndMakeVisible(pNormalButton);
      
      pBoldButton = new ToggleButton("BoldButton");
      pBoldButton->setButtonText(tr("Полужирный"));
      pBoldButton->addListener(this);
      // Радиокнопка не отмечена
      pBoldButton->setToggleState(false, false);
      pBoldButton->setRadioGroupId(1234);
      addAndMakeVisible(pBoldButton);
      
      pItalicButton = new ToggleButton("ItalicButton");
      pItalicButton->setButtonText(tr("Курсив"));
      pItalicButton->addListener(this);
      pItalicButton->setToggleState(false, false);
      pItalicButton->setRadioGroupId(1234);
      addAndMakeVisible(pItalicButton);
    
      setSize (600, 400);
    }
    //------------------------------------------------------
    

    Названия гарнитур системных шрифтов мы получаем с помощью метода static const StringArray Font::findAllTypefaceNames(), который возвращает массив строк (класс StringArray Juce), содержащий эти названия. Число элементов массива возвращается его методом int StringArray::size() const throw(). В цикле

    for(int i = 0; i < sSystemFonts.size(); i++)
    {
        pFontsBox->addItem(sSystemFonts[i], i + 1);
    } 
    

    мы перебираем по одному элементы списка и добавляем их в качестве элементов выпадающего списка pFontsBox.

    Элементы списка размера шрифтов(pFontSizeBox) добавляем в диапазоне от 8 до 20 пунктов.

    Заметим, что в Juce групповой блок служит исключительно в качестве элемента оформления; радиокнопки, рисуемые на его поверхности, не являются его потомками, а представляют собой самостоятельные виджеты. Для того, чтобы объединить их в одну группу, необходимо воспользоваться методом void Button::setRadioGroupId(int newGroupId). Если метод у нескольких радиокнопок принимает в качестве параметра какое-либо одно, отличное от нуля, число, то эти кнопки начинают действовать как единая радиогруппа, т.е. возможно включение одной и только одной кнопки из набора. В качестве ID для нашей радиогруппы используется число 1234.

    Деструктор и методы перерисовки и задания размеров нашего класса приведены в листинге 6.3 .

    TCentralComponent::~TCentralComponent()
    {
      // Удаляем дочерние виджеты
      // и обнуляем их указатели
      deleteAllChildren();
    }
    //-------------------------------------------------
    void TCentralComponent::paint(Graphics Canvas)
    {
      Canvas.fillAll(Colours::azure);
    }
    //-------------------------------------------------
    void TCentralComponent::resized()
    {
      pFontViewer->setBounds(proportionOfWidth(0.5505f), 8,    
                proportionOfWidth(0.4192f), 
                proportionOfHeight(0.9507f));
      pFontNameLabel->setBounds(8, 8,       
                roundFloatToInt((proportionOfWidth(0.4192f)) *  
                1.2048f), 25);
      pFontsBox->setBounds(8, 38, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontSizeLabel->setBounds (8, 68, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontSizeBox->setBounds(8, 96, 
                roundFloatToInt((proportionOfWidth(0.4192f)) * 
                1.2048f), 25);
      pFontStyleLabel->setBounds(8, 126, 
                 roundFloatToInt((proportionOfWidth(0.4192f)) * 
                 1.2048f), 25);
      pFontGroup->setBounds(8, 156, 
                 roundFloatToInt((proportionOfWidth(0.4192f)) * 
                 1.2048f), 196);
      pNormalButton->setBounds(32, 185, 150, 25);
      pBoldButton->setBounds(32, 216, 150, 25);
      pItalicButton->setBounds(32, 248, 150, 25);
    }
    

    Для задания относительных горизонтальных размеров виджетов мы использовали уже знакомую функцию int Component::proportionOfWidth(float proportion) const throw(). Для преобразования чисел с плавающей точкой, получаемых после расчёта координат компонентов, в целые использована функция из juce_MathsFunctions.h int roundFloatToInt(const float value) throw().

    Как видно из рисунка 6.1 , гарнитура и размер шрифта текста, отображаемого в многострочном текстовом поле TextEditor* pFontViewer, выбираются пользователем из выпадающих списков. Поскольку сам компонент-контейнер TCentralComponent является слушателем изменений списков (ComboBoxListener), в его классе мы переопределяем наследуемую от класса linstener'а виртуальную функцию virtual void ComboBox::Listener::ComboBoxChanged(ComboBox* ComboNoxThatHasChanged), которая отвечает за реакцию программы на выбор пользователя. Её реализация приведена в листинге 6.4 .

    void TCentralComponent::comboBoxChanged(ComboBox* pComboBox)
    {
      // Изменён выпадающий список гарнитуры шрифта
      if(pComboBox == pFontsBox)
      {
        // Устанавливаем в качестве шрифта текста поля ввода
        // шрифт, выбранный пользователем из списка
        CurrentFont.setTypefaceName(pFontsBox->getText());
      }
      // Изменён выпадающий список размера шрифта
      else if(pComboBox == pFontSizeBox)
      {
        // Вычисляем размер шрифта в пикселях,
        // исходя из выбранного пользователем кегля в типографских пунктах
        float fFontHeight = pFontSizeBox->getText().getFloatValue() * 96 
        / 72;
        // Устанавливаем размер шрифта
        CurrentFont.setHeight(fFontHeight);
      }
      // Задаём шрифт для отображения текста
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

    После того, как пользователь выбрал какой-либо пункт в выпадающем списке, отображаемый текст виджета изменяется соответственно надписи выбранного пункта. Получить этот текст можно с помощью функции const String ComboBox::getText() const. В случае первого списка (pFontsBox) это будет название выбранной пользователем гарнитуры шрифта.

    Это название передаём в качестве параметра в функию void void Font::setTypefaceName(const String faceName). Как понятно из названия, она устанавливает гарнитуру вызвавшего шрифта (объект класса Font) с названием, соответствующим строке-параметру. Если гарнитура с таким названием не будет найдена (шрифт не установлен на компьютере), то будет использоваться шрифт по умолчанию. В нашем случае такого происходить не должно, т.к. пользователь выбирает из ранее найденных названий шрифтов, установленных в системе.

    Во втором выпадающем списке,

    Теперь можно использовать шрифт с заданными пользователем параметрами для отображения текста в многострочном поле ввода pFontViewer. У класса TextEditor есть для этого два метода:

  • void TextEditor::setFont(const Font newFont), который устанавливает шрифт для вновь добавляемого в поле текста;
  • void TextEditor::applyFontToAllText(const Font newFont), который применяет параметры шрифта ко всему тексту поля ввода.
  • Понятно, что в нашем случае подходит именно второй метод ().

    Задать стиль текста в нашей демонстрационной программе пользователь может посредством трёх радиокнопок, которые мы отнесли к одной группе с ID 1234. Это обеспечивает выбор одной и только одной радиокнопки.

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

  • void Font::setBold(bool shouldBeBold) — задаёт полужирное начертание шрифта;
  • void Font::setItalic(bool shouldBeItalic) — задаёт курсивное начертание шрифта;
  • void Font::setUnderline(bool shouldBeUnderline) — задаёт подчёркнутое начертание шрифта.
  • Значение принимаемого параметра, равное true, задаёт соответствующий стиль шрифта, а равное false — его отменяет. Специальной функции для задания нормального стиля шрифта в Juce нет; для того, чтобы шрифт отображался нормальным начертанием, необходимо отменить все остальные стили с помощью вышеприведённых функций.

    Для того, чтобы узнать, какой стиль используется шрифтом, в классе Font библиотеки Juce есть следующие методы:

  • bool Font::isBold() const throw();
  • bool Font::isItalic() const throw();
  • bool Font::isUnderlined() const throw().
  • В том случае, если функция возвращает истину, начертание шрифта является полужирным, курсивным или подчёркнутым, соответственно.

    Попробуем применить эти сведения в нашей программе (см. первый вариант реализации функции ).

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      if (pButton == pNormalButton)
      {
        if(CurrentFont.isBold())
        {
          CurrentFont.setBold(false);
        }
        else if(CurrentFont.isItalic())
        {
          CurrentFont.setItalic(false);  
        }
      }
      else if (pButton == pBoldButton)
      {
        if(CurrentFont.isItalic())
        {
          CurrentFont.setItalic(false);
        }
        CurrentFont.setBold(true);
      }
      else if (pButton == pItalicButton)
      {
        if(CurrentFont.isBold())
        {
          CurrentFont.setBold(false);
        }
        CurrentFont.setItalic(true);
      }
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

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

    CurrentFont.setBold(true);
    CurrentFont.setItalic(true);
    

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

    Для того, чтобы задать один и только один стиль шрифта, нужно воспользоваться флагами стиля (упоминавшийся выше перечислимый тип FontStyleFlags. Они принимаются в качестве параметра функцией void Font::setStyleFlags(int newFlags), которая меняет стиль вызвавшего шрифта. Комбинация стилей в этой функции задаётся операцией побитового сложения (|).

    Перепишем реализацию функции ). Код стал значительно компактнее при сохранении функциональности программы.

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      if (pButton == pNormalButton)
      {
        // Задаём стиль шрифта - нормальный
        CurrentFont.setStyleFlags(Font::plain);
      }
      else if (pButton == pBoldButton)
      {
        // Задаём стиль шрифта - полужирный
        CurrentFont.setStyleFlags(Font::bold);
      }
      else if (pButton == pItalicButton)
      {   
        // Задаём стиль шрифта - курсив
        CurrentFont.setStyleFlags(Font::italic);
      }
      pFontViewer->applyFontToAllText(CurrentFont);
    }
    

    Для получения флага стиля того или иного шрифта используется функция int Font::getStyleFlags() const throw().

    Завершая описание класса Font библиотеки Juce, следует отметить, что для него доступны операторы присваивания = (копирует один шрифт в другой) и сравнения (== и !=).

    Строки в Juce

    Подобно многим другим GUI toolkit'ам в Juce представление строковых данных реализовано как класс (String). Мы уже встречались с этим классом в вышеприведённых программах.

    Класс String имеет несколько конструкторов:

  • String() throw() — создаёт пустую строку;
  • String(const String other) throw() — создаёт строку из другого объекта того же класса String;
  • String(const char* text) — создаёт строку из массива символов, оканчивающегося нулём;
  • String(const char* text, size_t maxChars) — создаёт строку из массива символов определённой длины;
  • String(const juce_wchar* unicodeText) — создаёт строку из массива символов в формате Юникод, оканчивающегося нулём; typedef wchar_t juce_wchar — платформонезависимый тип символа Юникод;
  • String(const juce_wchar* unicodeText, size_t maxChars) — создаёт строку из массива символов в формате Юникод определённой длины.
  • Класс String предоставляет целую серию методов и операторов, позволяющих проводить со строками разного рода операции: соединять их, осуществлять поиск подстрок и т.п.

    Для класса String имеется два перегруженных оператора: присваивания (=) и конкатенации (+=).

    Оператор = замещает содержимое объекта класса String содержимым другой строки.

    Оператор += осуществляет объединение или конкатенацию строк. При этом в конец исходной строки могут быть добавлены данные следующих типов:

  • символ (char);
  • символ Юникод (juce_wchar);
  • массив символов Юникод (juce_wchar*);
  • строка (String);
  • целое число (int);
  • беззнаковое целое (unsigned int).
  • Объединение строк можно осуществить также с помощью функции-члена класса String void String::append(const juce_wchar* textToAppend, int maxCharsToTake), где textToAppend — строка, добавляемая в конец исходной, а maxCharsToTake — максимальное число передаваемых символов.

    Строки можно сравнивать между собой с помощью набора методов класса:

  • bool String::equalsIgnoreCase(const String other) const throw() — сравнение с другой строкой без учёта регистра;
  • bool String::equalsIgnoreCase(const juce_wchar* other) const throw() — сравнение с другой строкой (представленной массивом символов Юникод) без учёта регистра;
  • bool String::equalsIgnoreCase(const char* other) const throw() — сравнение с другой строкой (представленной массивом символов) без учёта регистра;
  • int String::compare(const String other) const throw() — сравнение с другой строкой с учётом регистра;
  • int String::compare(const char* other) const throw() — сравнение с другой строкой (представленной массивом символов) с учётом регистра;
  • int String::compare(const juce_wchar* other) const throw() — сравнение с другой строкой (представленной массивом символов Юникод) с учётом регистра;
  • int String::compareIgnoreCase(const String other) const throw() — сравнение с другой строкой без учёта регистра;
  • int String::compareLexicographically(const String other) const throw() — лексикографическое сравнение с другой строкой.
  • Узнать, является ли строка пустой, можно с помощью следующих методов:
  • bool String::isEmpty() const throw() — возвращает true, если строка не содержит ни одного символа;
  • bool String::isNotEmpty() const throw() — возвращает true, если строка содержит хотя бы один символ.
  • Того же результата можно добиться, проверив длину строки в символах методом int String::length() const throw().

    Следует помнить, что строка, содержащая пробелы, пустой не является, поэтому функция isEmpty, вызываемая ею, будет возвращать false. Как правило, пользователь воспринимает текстовое поле, в котором нет других символов, кроме пробелов, именно как пустое. Библиотека Juce включает удобную функцию bool String::containsNonWhitespaceChars() const throw(), которая возвращает true, если вызывающая строка содержит какие-либо символы, помимо пробелов.

    Поиск строк и символов в тексте — одна из наиболее часто возникающих задач при разработке приложений. Класс String содержит довольно много методов для её решения.

    Прежде всего, это методы поиска подстроки:

  • const String String::substring(int startIndex) const — возвращает часть вызывающей строки от символа startIndex и до её конца;
  • const String String::substring(int startIndex, int endIndex) const — возвращает часть вызывающей строки между символами startIndex и endIndex.
  • Для того, чтобы узнать, содержит ли строка подстроку или символы (в любой позиции), используются следующие функции:

  • bool String::contains(const String text) const throw() — проверяет, содержит ли строка подстроку text. Чувствительна к регистру. Если в качестве параметра была передана пустая строка, функция всё равно возвращает true.
  • bool String::containsIgnoreCase(const String text) const throw() — работает аналогично предыдущей функции, но не учитывает регистр символов.
  • bool String::containsChar(juce_wchar character) const throw() — проверяет, содержит ли строка символ, переданный в качестве параметра.
  • bool String::containsAnyOf(const String charactersItMightContain) const throw() — возвращает true, если в вызывающей строке есть какие-либо символы из набора, принимаемого в качестве параметра.
  • bool String::containsOnly(const String charactersItMightContain) const throw() — возвращает false, если в вызывающей строке отсутствует какой-либо из символов набора, принимаемого в качестве параметра.
  • bool String::containsWholeWord(const String wordToLookFor) const throw() — возвращает true, если вызывающая метод строка содержит слово wordToLookFor, окружённое символами, не являющимися буквами и цифрами.
  • Для замены определённой части строки другой класс String предоставляет метод const String String::replace(const String stringToReplace, const String stringToInsertInstead, bool ignoreCase = false) const, все вхождения подстроки stringToReplace строкой stringToInsertInstead с учётом или без учёта регистра в зависимости от принимаемого значения параметра ignoreCase.

    Для замены одного символа в строке можно воспользоваться функцией const String String::replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const. Кроме того, класс String включает метод, который позволяет заменить один набор символов другим: const String String::replaceCharacters(const String charactersToReplace, const String charactersToInsertInstead) const. Он возвращает строку, в которой каждый символ из первого набора (charactersToReplace) замещается соответствующим символом из второго набора (charactersToInsertInstead) в эквивалентной позиции. Понятно, что передаваемые в функцию две строки должны быть одинакового размера. Например, при вызове replaceCharacters("abc", "def") в результирующей строке "a" замещается на "d", "b"на "e" и т.п.

    Близким к предыдущей функции является метод класса String const String String::retainCharacters(const String charactersToRetain) const, который возвращает строку, сохраняющую только тот набор символов, содержавшихся в вызвавшей строке, который был передан в качестве параметра. Например, если объект класса String, содержащий строку "1122334455", обратится к методу retainCharacters("432"), то будет возвращена строка "223344".

    Для преобразования данных строки в верхний или нижний регистр используются методы const String String::toLowerCase() const и const String String::toUpperCase() const, которые возвращают, соответственно, вариант вызвавшей строки в нижнем и верхнем регистре.

    Достаточно часто встречается ситуация, когда необходимо конвертировать числовые значения в строковые и наоборот. В первом случае необходимо создать объект класса String, в качестве параметра конструктора которого передать конвертируемое число. Принимаемыми в качестве параметра конструктора типами могут быть short int, unsigned short int, int, unsigned int, int64, uint64, float и double. Например:

    String sNumber(1234); // Строка содержит текст "1234".
    

    Преобразование из строкового в числовое значение производится методами, содержащими в своём имени название типа:

  • int String::getIntValue() const throw();
  • int64 String::getLargeIntValue() const throw();
  • float String::getFloatValue() const throw();
  • double String::getDoubleValue() const throw();
  • int String::getHexValue32() const throw();
  • int64 String::getHexValue64() const throw();
  • Последние две функции возвращают шестнадцатеричное число соответствующего размера; прочие символы, если их содержит строка, игнорируются. Пример использования функции getFloatValue приведён в листинге 6.4 .

    Кроме того, класс String включает довольно интересный метод int String::getTrailingIntValue() const throw(), который возвращает целое число, находящееся в конце строки. Например:

    String sNumber("321 xyz654");
    // Функция возвращает 654
    int iNumber = sNumber.getTrailingIntValue()
    

    Список строк (класс StringArray)

    Для хранения массива строковых объектов в Juce имеется класс StringArray, который описывает список строк с расширенными возможностями манипулирования ими.

    Класс поддерживает следующие операции работы со строками:

    Доступ к строке по её индексу возможен с помощью оператора [], как к элементу обычного массива. Если индекс выходит за пределы массива, будет возвращена пустая строка. Кроме того, получить ссылку на одну из строк массива по её индексу позволяет метод класса String StringArray::getReference(int index) throw().

    Возвращение числа элементов списка (хранящихся строк) осуществляет метод int StringArray::size() const throw().

    Добавление новых строк в конец списка. Производится функцией void StringArray::add(const String stringToAdd). Кроме того, в конец списка можно добавить целый массив строк, другой объект класса StringArray, с помощью метода void StringArray::addArray(const StringArray other, int startIndex = 0, int numElementsToAdd = -1), где other — первый элемент добавляемого строкового массива, с которого необходимо начинать вставку, а numElementsToAdd — максимальное число строк из добавляемого массива для вставки. Если значение параметра меньше нуля, то добавляются все элементы.

    Вставка новых строк в произвольное место списка со смещением элементов массива. Функция void StringArray::insert(int index, const String stringToAdd) вставляет строку stringToAdd в массив на место, заданное индексом index, поднимая другие элементы. Если значение индекса меньше нуля или превышает размер массива, то строка добавляется в конец списка.

    Вставка новых строк в произвольное место списка с заменой элементов массива. Функция void StringArray::set(int index, const String newString) вставляет строку newString в массив на место, заданное индексом index. При этом прежняя строка замещается новой. Если значение индекса меньше нуля функция не делает ничего, а если оно превышает размер массива, то строка добавляется в конец списка.

    Поиск строк в массиве (по содержимому). Чаще бывает, что найти необходимую строку в массиве бывает нужно не по её индексу, а по тексту, в ней содержащемуся. В этом может помочь метод bool StringArray::contains(const String stringToLookFor, bool ignoreCase = false) const. Также, как и раньше, второй параметр — флаг, следует ли при поиске строки stringToLookFor учитывать регистр.

    Удаление строк. Удалить одну строку, находящуюся на позиции index, из списка можно с помощью функции void StringArray::remove(int index). Если индекс выходит за пределы массива, то функция ничего не делает. Поиск строки stringToRemove в массиве с последующим её удалением выполняет функция void StringArray::removeString(const String stringToRemove, bool ignoreCase = false); второй её параметр — флаг, показывающий, следует ли игнорировать регистр.

    Полезны также функции, позволяющие удалить все повторяющиеся строки, void StringArray::removeDuplicates(bool ignoreCase), и все пустые строки в массиве, void StringArray::removeEmptyStrings(bool removeWhiteSpaceStrings = true). Если последняя функция в качестве параметра принимает истину, то пустыми будут считаться также строки, заполненные только пробелами.

    Кроме того, класс StringArray позволяет удалять сразу ряд строк. Для этого используется метод void StringArray::removeRange(int startIndex, int numberToRemove), где startIndex — индекс первой строки из ряда, подлежащего удалению, а numberToRemove — число удаляемых элементов.

    Перемещение строк на новую позицию в массиве осуществляет метод void StringArray::move(int currentIndex, int newIndex) throw(). При вызове функции экземпляром класса StringArray происходит перемещение содержащейся в нём строки, находящейся на позиции currentIndex, на позицию newIndex.

    Сортировка элементов списка осуществляется функцией void StringArray::sort(bool ignoreCase); параметр указывает, следует ли учитывать регистр. Строки сортируются в алфавитном порядке.

    Очистка списка. Удаление всех строк из списка с обнулением его размера выполняет функция void StringArray::clear().

    Нахождение индекса строки осуществляет функция int StringArray::indexOf(const String stringToLookFor, bool ignoreCase = false, int startIndex = 0) const, где stringToLookFor — текст (строка) для поиска, ignoreCase— флаг, показывающий, следует ли игнорировать регистр, а startIndex — индекс элемента, с которого начинается поиск. Функция возвращает индекс найденной строки, а в случае, если ничего не было найдено, возвращается -1.

    Объединение строк массива в одну. Удобно для автоматической генерации текста, добавляемого, скажем, в многострочное поле ввода на основании собранных и сохранённых в массиве StringArray данных. Осуществляется функцией const String StringArray::joinIntoString(const String separatorString, int startIndex = 0, int numberOfElements = -1) const.

    Мы можем с помощью параметра separatorString задать строку-разделитель, которая будет разделять элементы массива в результирующей строке. Например, если массив StringArray включал строки "a", "b" и "c", то вызов метода joinIntoString(",") вернёт строку "a,b,c".

    startIndex, как нетрудно догадаться, индекс элемента массива, с которого следует начать объединение, а numberOfElements — число объединяемых строк. В том случае, если последний параметр принимает значение меньше нуля, то будут объединены все строки массива.

    Краткие итоги

    Для вывода неформатированного текста в Juce используется класс String, а форматированного — также и класс Font, хранящий информацию о параметрах шрифта текста.

    Класс String, инкапсулирует действия со строками. Объект класса String — это строка, состоящая из произвольного числа символов. Литерные константы типа String представляют собой последовательности символов, заключённые в двойные кавычки.

    Дополнительные материалы

    Архив с исходными текстами примера Вы можете скачать здесь

    Вернуться к учебному плану