IscanderChe
IscanderChe15 червня 2020 р. 06: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».

Після цього відкривається основне вікно The IDE.
Зліва зверху розташоване вікно, де відображаються підключені до пакета інші пакети. Ліворуч знизу — вікно зі згенерованими щойно файлами нашого пакета. Справа відображається структура поточного файлу пакета. По центру – редактор коду.
Всього створено три файли: 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 р. 07:08

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

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

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

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

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

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

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

IscanderChe
  • 15 червня 2020 р. 07:47
  • (відредаговано)

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

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

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

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

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

Не смотрел.

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

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

Evgenii Legotckoi
  • 15 червня 2020 р. 07:51

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Дмитрий

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

  • Результат:60бали,
  • Рейтинг балів-1
Дмитрий

C++ - Тест 003. Условия и циклы

  • Результат:92бали,
  • Рейтинг балів8
d
  • dsfs
  • 26 квітня 2024 р. 04:56

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

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
k
kmssr08 лютого 2024 р. 18:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 01:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 10:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 08:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 грудня 2023 р. 21:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
George1307 травня 2024 р. 00:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT05 травня 2024 р. 05:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son03 травня 2024 р. 17:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi02 травня 2024 р. 14:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 квітня 2024 р. 04:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Слідкуйте за нами в соціальних мережах