IscanderChe
IscanderCheМаусым 15, 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 макросами.

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

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

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

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

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз