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 макросами.

Комментарии

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

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 1:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 1:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 11:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 14:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 8:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 7:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 11:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
m
moogo22 ноября 2024 г. 7:17
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 6:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 3:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

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