IscanderCheJune 15, 2020, 6:51 a.m.

Простой текстовый редактор на 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 был прописан абсолютный путь к файлу. Пришлось подключать повторно.)

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

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

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

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

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

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

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

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

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

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

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

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

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

Не смотрел.

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

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

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
How to become an author?

Contribute to the evolution of the EVILEG community.

Learn how to become a site author.

Learn it
Donate

Good day, Dear Users!!!

I am Evgenii Legotckoi, developer of EVILEG. And it is my hobby project, which helps to learn programming another programmers and developers

If the site helped you, and you want also support the development of the site, than you can donate by following ways

PayPalYandex.Money
Timeweb

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting
R

C++ - Test 002. Constants

  • Result:75points,
  • Rating points2
R

C++ - Test 001. The first program and data types

  • Result:73points,
  • Rating points1
MS

C++ - Test 005. Structures and Classes

  • Result:75points,
  • Rating points2
Last comments
R

Qt WinAPI - Lesson 001. How to collect all DLL, which used in Qt project?

Вы меня не совсем правильно поняли, но все равно спасибо, принял все к сведению. Все сделал как вы сказали, все отлично работает, еще раз огромнейшее спасибо) Разве что только что были опять про…

Qt WinAPI - Lesson 001. How to collect all DLL, which used in Qt project?

Стоило перед использованием что ли инструкцию прочитать https://www.cyberforum.ru/blogs/131347/blog2457.html "После сборки при запуске требовались dll," Ясное дело стоило задепло…
R
R

Qt WinAPI - Lesson 001. How to collect all DLL, which used in Qt project?

Да, собралось. После сборки при запуске требовались dll, перекинул всю папки bin, plugins(не знаю как можно было сделать более умно). Как я понял в первой строке путь к екзешнику вставляю, втор…

Android. Java vs Qt QML - Tutorial 000. Enable Material Design

Это актуально для изменения цвета. В файле qtquickcontrols2.conf переменная Primary должна влиять на цвет приложения соответственно и цвет ApplicationBar должен поменяться. Но у status bar вроде…
Now discuss on the forum

Qt C++ и Python

Красиво/некрасиво - это скорее моё личное отношение. Если есть возможность ограничить количество интсрументов, то лучше ограничить. Но не зацикливайтесь на этом. Если у вас есть скрипты Py…

Qt + OpenGL glDeleteVertexArrays

Я не уверен, поскольку с OpenGL очень мало работал. Но может быть OpenGL контекст виджета нужно переинициализовывать. И ещё виджет стоит удалять через метод deleteLater() а не п…

QWebEngineView не запускается если к ПК подключено несколько мониторов

Ну я имел ввиду посмотреть на другом ПК с другой графикой и парой мониторов. Как моей программе назначить использовать определенный граф. адаптер? Вот тут понятия не имею.

Счечик производительности сети

Хорошо. После работы сегодня гляну ваш код внимательно.

Как в Qt в qmenu добавить scrollarea

Вот это наследованный класс меню. Но посути это обычное меню. #pragma once#include <QtWidgets>class TransMenu : public QMenu { Q_OBJECTpublic: TransMenu(QWidget* parent = …
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB