IscanderChe
IscanderChe15 июня 2020 г. 6:51

Простой текстовый редактор на Ultimate++

Об Ultimate++ ( https://www.ultimatepp.org/ ) я узнал совершенно случайно, когда прочитал в айтишной новостной RSS-ленте об очередном обновлении этого фреймворка. Что ж, подумал я, раз мои пен-проекты пока в заторможенном состоянии, почему бы не попробовать что-то новое.

Ultimate++ (сокращённо U++ или UPP) представляет собой кроссплатформенный фреймворк для разработки GUI-приложений на основе C++. Кроме кода самого фреймворка в состав входит собственный сборщик BLITZ и среда разработки TheIDE для редактирования кода и элементов GUI.

Попробуем написать простейший блокнот на основе U++.


Скачиваем с сайта zip-архив с фрейворком, распаковываем в любую удобную директорию и запускаем среду разработки TheIDE.
Первое, что предлагается средой — выбрать пакет (package) или создать новый пакет приложения. Есть ещё вариант — создать новую коллекцию (assembly) пакетов. Кроме того, можно выбрать существующие коллекции. Из существующих коллекций доступны различные примеры.

Итак, создадим новую коллекцию. Для этого в поле коллекций нужно вызвать контекстное меню и выбрать пункт «New assembly» (Новая коллекция). В открывшемся окне имеется три строчных поля ввода: «Package nests» (Гнёзда пакетов), «Output directory» (Директория вывода), «Assembly name» (Имя коллекции). В «Package nests» уже внесённые пути к пакетам лучше не удалять. А вот добавить свой путь необходимо. Вызываем редактор гнёзд пакетов, нажав на кнопку с многоточием справа от поля ввода. В открывшемся окне вносим удобный путь к своим файлам, перемещаем кнопками со стрелками этот путь на первое место и нажимаем «ОК». В «Output directory», в свою очередь, вносим удобный путь для выходных файлов после компиляции и линковки. И пишем в «Assembly name» имя, которым коллекция будет представлена в списке коллекций. Всё, жмём «ОК».

Теперь создадим новый пакет. Для этого нажимаем кнопку «New package», расположенную в нижнем правом углу основного окна TheIDE.
В открывшемся диалоговом окне есть несколько вариантов создания пакета. Выбираем «U++ CtrlLib application with main window», с использованием библиотеки CtrlLib, куда, помимо прочего, входят интересующие нас экранные виджеты, и лэйаутом главного окна. Вводим в соответствующее поле имя пакета и нажимаем «OK».

После этого открывается основное окно TheIDE.
Слева сверху расположено окно, где отображаются подключённые к пакету другие пакеты. Слева снизу — окно со сгенерированными только что файлами нашего пакета. Справа показывается структура текущего файла пакета. По центру — редактор кода.
Всего создано три файла: UppNotepad2.h , main.cpp и UppNotepad2.lay . Предупрежу вопросы, почему «2»: с первой версией блокнота я ошибся, и при выборе типа пакета указал простое приложение без главного окна, и файл *.lay не был создан, а без него нам не обойтись.

С первыми двумя файлами всё понятно, это исходный код нашего блокнота. Третий файл, UppNotepad2.lay – лэйаут главного окна приложения. Рассмотрим каждый из них подробно.

#ifndef _UppNotepad2_UppNotepad2_h
#define _UppNotepad2_UppNotepad2_h

#include <CtrlLib/CtrlLib.h>

using namespace Upp;

#define LAYOUTFILE <UppNotepad2/UppNotepad2.lay>
#include <CtrlCore/lay.h>

class UppNotepad2 : public WithUppNotepad2Layout<TopWindow>
{
public:
    typedef UppNotepad2 CLASSNAME;
    UppNotepad2();
};

#endif

#include "UppNotepad2.h"

UppNotepad2::UppNotepad2()
{
    CtrlLayout(*this);
}

GUI_APP_MAIN
{
    UppNotepad2().Run();
}

Этот код генерируется автоматически и в редактировании не нуждается.

Для лэйаута существует два режима просмотра: в графическом и текстовом виде.

LAYOUT(ppNotepad2Layout, 200, 100)
END_LAYOUT

Есть ещё четвёртый файл, который размещён в директории пакета — UppNotepad2.upp . Этот файл в TheIDE не показывается, он нужен только для сборки пакета.

Начнём разрабатывать наше приложение с лэйаута.
Курсор наводим на серое поле лэйаута и в контекстном меню последовательно выбираем: «Editors – DocEdit». На лэйауте таким образом появляется виджет редактирования текста. Настроим его. Для этого выделим виджет. В средней части внизу появляется окно со свойствами элемента. Справа от типа элемента есть поле ввода для имени виджета. Введём туда «editor».

Также настроим тип и размер шрифта. Для этого щёлкнем по полю ввода "SetFont" и открывшемся диалоговом окне укажем тип шрифта "Monospace", размер 12.

И настроим зависимость расположения элемента от размеров главного окна.

Теперь перейдём к заголовочному файлу.

Объявим элементы главного окна и переменные:
- MenuBar menu — строка выпадающих меню;
- ToolBar tool — панель инструментов;
- StatusBar status — строка состояния;
- String fileName — имя редактируемого файла;
- String loadText — загруженный текст файла.

Объявим следующие методы для работы с текстом и файлами:
- void OpenCmd() - открыть файл;
- void SaveCmd() - сохранить файл;
- void SaveAsCmd() - сохранить файл как;
- void ExitCmd() - закрыть приложение;
- void CutCmd() - вырезать текст;
- void CopyCmd() - копировать текст;
- void PasteCmd() - вставить текст;
- void UndoCmd() - отменить последнее действие;
- void RedoCmd() - повторить последнее действие.

Также переопределим метод virtual void Close() окна TopWindow для случая, когда нажата кнопка закрытия окна.

И определим методы, формирующие меню на основе библиотечного элемента Bar :
- void FileBar(Bar& bar) — меню/панель инструментов «Файл»;
- void EditBar(Bar& bar) — меню/панель инструментов «Редактировать»;
- void MainBar(Bar& bar) — панель инструментов;
- void MainMenu(Bar& bar) — выпадающее меню.

class UppNotepad2 : public WithUppNotepad2Layout<TopWindow>
{
public:
    typedef UppNotepad2 CLASSNAME;
    UppNotepad2();

    MenuBar menu;
    ToolBar tool;
    StatusBar status;
    String fileName;
    String loadText;

    void OpenCmd();
    void SaveCmd();
    void SaveAsCmd();
    void ExitCmd();

    void CutCmd();
    void CopyCmd();
    void PasteCmd();
    void UndoCmd();
    void RedoCmd();

    void FileBar(Bar& bar);
    void EditBar(Bar& bar);
    void MainBar(Bar& bar);
    void MainMenu(Bar& bar);

    virtual void Close();
};

Перейдём к файлу реализации.
Для открытия файлов служит метод UppNotepad2::OpenCmd() . В нём используется диалог FileSel для выбора файлов, метод Type диалога для настройки отображения файлов с расширением *.txt и метод Get() для получения полного пути к файлу. Кроме того, используется утилита FileIn для открытия файла на чтение и утилита LoadFile для загрузки файла в текстовый редактор.

void UppNotepad2::OpenCmd()
{
    FileSel fs;
    fs.Type("Текстовые файлы (*.txt)", "*.txt");
    if(fs.ExecuteOpen("Открыть файл"))
    {
        fileName = fs.Get();
        FileIn fi(fileName);
        if(!fi)
        {
            PromptOK("Не могу открыть файл!");
            return;
        }
        loadText = LoadFile(fileName);
        editor.Set(loadText);
    }
}

В методах UppNotepad2::SaveCmd() и UppNotepad2::SaveAsCmd() используется утилита SaveFile для сохранения файла.

void UppNotepad2::SaveCmd()
{
    if(!SaveFile(fileName, editor.Get()))
        PromptOK("Не могу сохранить файл!");
}

void UppNotepad2::SaveAsCmd()
{
    FileSel fs;
    fs.Type("Текстовые файлы (*.txt)", "*.txt");
    if(fs.ExecuteSaveAs("Сохранить файл как"))
    {
        fileName = fs.Get();
        if(!SaveFile(fileName, editor.Get()))
            PromptOK("Не могу сохранить файл!");
    }
}

В методе ExitCmd() проверяется, изменён ли текст, и если изменён, пользователю задаётся вопрос, сохранить ли текст. В случае, если пользователь соглашается сохранить текст, вызывается либо SaveCmd() , если файл был ранее открыт, либо SaveAsCmd() , если файл новый.

void UppNotepad2::ExitCmd()
{
    String curText = editor.Get();
    if(!(loadText == curText))
    {
        if(PromptOKCancel("Сохранить файл?"))
        {
            if(!fileName.IsEmpty())
                SaveCmd();
            else
                SaveAsCmd();
        }
    }
    Break();
}

void UppNotepad2::Close()
{
    ExitCmd();
}

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

void UppNotepad2::CutCmd()
{
    editor.Cut();
}

void UppNotepad2::CopyCmd()
{
    editor.Copy();
}

void UppNotepad2::PasteCmd()
{
    editor.Paste();
}

void UppNotepad2::UndoCmd()
{
    editor.Undo();
}

void UppNotepad2::RedoCmd()
{
    editor.Redo();
}

Рассмотрим код создания меню.
За добавление пункта выпадающего меню и кнопки панели инструментов отвечает метод Add() , причём работает он по разному в зависимости от аргументов. Если указан путь к иконке, как это сделано, например, для команды «Сохранить», то создастся и пункт выпадающего меню, и кнопка на панели инструментов. Если иконка не указана (см. команду «Сохранить как…»), то создаётся только пункт выпадающего меню. Макрос THISBACK позволяет подключить соответствующий метод к создаваемому элементу меню. В методе Help() вписывается строка контекстной помощи, отображающейся в строке состояния. Метод Separator() добавляет разделительную полосу как в выпадающее меню, так и в панель инструментов.

void UppNotepad2::FileBar(Bar& bar)
{
    bar.Add("Открыть", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\open.png"), THISBACK(OpenCmd)).Help("Открыть файл");
    bar.Add("Сохранить", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\save.png"), THISBACK(SaveCmd)).Help("Сохранить файл");
    bar.Add("Сохранить как...", THISBACK(SaveAsCmd)).Help("Сохранить как");
    bar.Add("Выйти", THISBACK(ExitCmd)).Help("Выход из программы");
}

void UppNotepad2::EditBar(Bar& bar)
{
    bar.Add("Вырезать (Ctrl + X)", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\cut.png"), THISBACK(CutCmd)).Help("Вырезать выделенный текст в буфер");
    bar.Add("Копировать (Ctrl + C)", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\copy.png"), THISBACK(CopyCmd)).Help("Копировать выделенный текст в буфер");
    bar.Add("Вставить (Ctrl + V)", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\paste.png"), THISBACK(PasteCmd)).Help("Вставить текст из буфера");
    bar.Separator();
    bar.Add("Отменить (Ctrl + Z)", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\undo.png"), THISBACK(UndoCmd)).Help("Отменить последнее действие");
    bar.Add("Повторить (Ctrl + Shift + Z)", StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\redo.png"), THISBACK(RedoCmd)).Help("Повторить последнее действие");
}

void UppNotepad2::MainBar(Bar& bar)
{
    FileBar(bar);
    bar.Separator();
    EditBar(bar);
}

void UppNotepad2::MainMenu(Bar& bar)
{
    bar.Add("Файл", THISBACK(FileBar));
    bar.Add("Редактировать", THISBACK(EditBar));
}

Теперь рассмотрим конструктор UppNotepad2 .

UppNotepad2::UppNotepad2()
{
    // Устанавливаем лэйаут
    CtrlLayout(*this);

    loadText = "";

    // Устанавливаем цвет текста
    editor.SetColor(TextCtrl::INK_NORMAL, Color(255, 255, 255));
    // Устанавливаем цвет фона
    editor.SetColor(TextCtrl::PAPER_NORMAL, Color(0, 0, 0));
    // Убираем горизонатльную полосу, обозначающую конец файла
    editor.EofLine(false);

    // Устанавливаем заголовок окна, указываем, что окно будет изменяемым, с кнопками минимизации и развёртывания окна
    // и что окно будет развёрнуто максимально
    Title("Upp Notepad 2").Sizeable().MinimizeBox().MaximizeBox().Maximize(true);
    // Устанавливаем иконку заголовка окна
    Icon(StreamRaster::LoadFileAny("E:\\dev\\upp\\UppNotepad2\\images\\editor.png"));
    // Добавляем в окно выпадающее меню
    AddFrame(menu);
    // Добавляем разделитель выпадающего меню и панели инструментов
    AddFrame(TopSeparatorFrame());
    // Добавляем панель инструментов
    AddFrame(tool);
    // Добавляем строку состояния
    AddFrame(status);
    // Устанавливаем меню
    menu.Set(THISBACK(MainMenu));
    // Перенаправляем вывод Help() в строку состояния
    menu.WhenHelp = status;
    // Устанавливаем панель инструментов
    tool.Set(THISBACK(MainBar));
    tool.WhenHelp = status;
}

Для сокрытия платформозависимых деталей реализации вместо функции main() используется макрос GUI_APP_MAIN .

GUI_APP_MAIN
{
    UppNotepad2().Run();
}

Вот результат выполнения кода.

Чтобы добавить иконку приложения для рабочего стола, необходимо подключить файл *.rc. Для этого в любом текстовом редакторе пишем следующую строку:

9999 ICON "images/editor.ico"

и сохраняем её в файл UppNotepad.rc в каталоге с исходниками нашего приложения. Далее в поле файлов TheIDE вызываем контекстное меню и выбираем пункт «Insert package directory file(s)» и выбираем только что созданный файл. Это необходимо, чтобы файл *.rc попал в пакет и участвовал в сборке пакета.

Выводы

Достоинства фреймворка:
- не требуется установка дистрибутива, достаточно распаковать zip-архив в подходящую директорию, и все инструменты становятся доступны;
- обширная база примеров;
- простота освоения;
- хорошая документация;
- на выходе получается один исполняемый файл, не требующий никаких зависимостей.

Недостатки:
- на сайте легче воспользоваться поиском по сайту, чем в некоторых случаях переходить по битым ссылкам;
- одно открытое окно TheIDE поддерживает только один открытый головной проект-пакет. Это обходится тем, что можно открыть второй и последующие экземпляры TheIDE. В каком-то смысле так даже удобнее, переключаться между окнами можно с помощью Alt+Tab;
- в редакторе текстов TheIDE не работает автодополнение для ключевых слов и переменных, которые начинаешь вводить с новой строки;
- виджеты GUI можно создать только в лэйауте, через код их создавать нельзя;
- непереносимость кода с машины на машину. (Код редактора я писал на одном компьютере, повторную проверку работоспособности кода проверял на другой машине. В результате «отвалился» rc-файл и выдавал ошибку при компиляции. Это связано с тем, что в файле *.upp был прописан абсолютный путь к файлу. Пришлось подключать повторно.)

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Evgenii Legotckoi
  • 15 июня 2020 г. 7:08

виджеты GUI можно создать только в лэйауте, через код их создавать нельзя;

Это довольно сильный недостаток. Может всё-таки возможно из кода производить редактирование?

непереносимость кода с машины на машину.

Там наверняка можно задать относительный путь. Как бы это довольно детский недостаток для фреймворка. Должно было быть вылечено ещё на ранних стадиях.

Макрос THISBACK позволяет подключить соответствующий метод к создаваемому элементу меню

Не смотрели, что там внутри макроса? Там обычный callback на шаблонах?

Не смотрели, как обстоит дело с поддержкой мультиязычности?

IscanderChe
  • 15 июня 2020 г. 7:47
  • (ред.)

Может всё-таки возможно из кода производить редактирование?

По примерам - только так. Но некоторые, сложные виджеты, реализованы как раз через код. После Qt, конечно, тёмный лес. :))

Там наверняка можно задать относительный путь.

Возможно, что я не той опцией на первой машине воспользовался. Сейчас стоит как относительный путь.

Не смотрели, что там внутри макроса? Там обычный callback на шаблонах?

Не смотрел.

Не смотрели, как обстоит дело с поддержкой мультиязычности?

Мультиязычность есть, но я подробно не разбирался.

Evgenii Legotckoi
  • 15 июня 2020 г. 7:51

Фигово, что подключение функций реализовано на макросах. Напоминает Qt4 с их SIGNAL и SLOT макросами.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

C++ - Тест 001. Первая программа и типы данных

  • Результат:46баллов,
  • Очки рейтинга-6
FL

C++ - Тест 006. Перечисления

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr8 февраля 2024 г. 15:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко4 февраля 2024 г. 22:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 7:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 5:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 18:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 1:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 8:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 5:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 1:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 3:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях