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

A simple text editor in Ultimate ++

I learned about Ultimate++ ( https://www.ultimatepp.org/ ) quite by accident when I read in an IT RSS feed about the next update of this framework. Well, I thought, since my pen projects are on hold, why not try something new.

Ultimate++ (abbreviated as U++ or UPP) is a cross-platform framework for developing GUI applications based on C++. In addition to the code of the framework itself, it includes its own BLITZ assembler and TheIDE development environment for editing code and GUI elements.

Let's try to write a simple notepad based on U++.


We download the zip-archive with the framework from the site, unpack it into any convenient directory and launch the TheIDE development environment.
The first thing the environment prompts you to do is select a package or create a new application package. There is another option - to create a new collection (assembly) of packages. You can also select existing collections. Various examples are available from existing collections.

So let's create a new collection. To do this, in the field of collections, you need to call the context menu and select the item "New assembly" (New collection). The window that opens has three inline input fields: “Package nests”, “Output directory”, “Assembly name”. In "Package nests" it is better not to delete already entered paths to packages. But you need to add your path. We call the package nest editor by clicking on the button with an ellipsis to the right of the input field. In the window that opens, enter a convenient path to your files, use the arrow buttons to move this path to the first place and click "OK". In "Output directory", in turn, we make a convenient path for output files after compilation and linking. And we write in the "Assembly name" the name by which the collection will be presented in the list of collections. Everything, click "OK".

Now let's create a new package. To do this, click the "New package" button located in the lower right corner of the main TheIDE window.
In the dialog box that opens, there are several options for creating a package. Select "U++ CtrlLib application with main window", using the CtrlLib library, which, among other things, includes the screen widgets we are interested in, and the layout of the main window. Enter the name of the package in the appropriate field and click "OK".

After that, the main window of TheIDE opens.
At the top left there is a window where other packages connected to the package are displayed. On the bottom left is a window with the newly generated files of our package. The structure of the current package file is shown on the right. In the center is the code editor.
There are three files in total: UppNotepad2.h , main.cpp and UppNotepad2.lay . I will warn questions why “2”: I made a mistake with the first version of the notepad, and when choosing the package type, I indicated a simple application without the main window, and the * .lay file was not created, and we cannot do without it.

With the first two files, everything is clear, this is the source code of our notebook. The third file, UppNotepad2.lay is the layout of the main application window. Let's consider each of them in detail.

#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();
}

This code is generated automatically and does not need to be edited.

For the layout, there are two viewing modes: in graphic and text form.

LAYOUT(ppNotepad2Layout, 200, 100)
END_LAYOUT

There is also a fourth file, which is located in the package directory - UppNotepad2.upp . This file is not shown in TheIDE, it is only needed to build the package.

Let's start developing our application from the layout.
Move the cursor over the gray layout field and select in the context menu: "Editors - DocEdit". This is how the text editing widget appears on the layout. Let's set it up. To do this, select the widget. In the middle part below, a window with the properties of the element appears. To the right of the element type there is an input field for the widget name. Let's enter "editor" there.

We will also set the font type and size. To do this, click on the "SetFont" input field and in the dialog box that opens, specify the font type "Monospace", size 12.

And configure the dependence of the element location on the size of the main window.

Now let's move on to the header file.

Let's declare the elements of the main window and variables:
- MenuBar menu — drop-down menu bar;
- ToolBar tool - toolbar;
- StatusBar status — status bar;
- String fileName — name of the file being edited;
- String loadText — loaded file text.

Let's declare the following methods for working with text and files:
- void OpenCmd() - open file;
- void SaveCmd() - save file;
- void SaveAsCmd() - save file as;
- void ExitCmd() - close the application;
- void CutCmd() - cut text;
- void CopyCmd() - copy text;
- void PasteCmd() - paste text;
- void UndoCmd() - undo the last action;
- void RedoCmd() - repeat the last action.

We will also override the virtual void Close() method of the TopWindow window for the case when the button to close the window is pressed.

And define the methods that form the menu based on the Bar library element:
- void FileBar(Bar& bar) — File menu/toolbar;
- void EditBar(Bar& bar) — Edit menu/toolbar;
- void MainBar(Bar& bar) - toolbar;
- void MainMenu(Bar& bar) - dropdown menu.

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();
};

Let's move on to the implementation file.
The UppNotepad2::OpenCmd() method is used to open files. It uses the FileSel dialog to select files, the Type method of the dialog to configure the display of *.txt files, and the Get() method to get the full path to the file. In addition, the FileIn utility is used to open the file for reading, and the LoadFile utility is used to load the file into a text editor.

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);
    }
}

The UppNotepad2::SaveCmd() and UppNotepad2::SaveAsCmd() methods use the SaveFile utility to save the file.

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("Не могу сохранить файл!");
    }
}

The ExitCmd() method checks to see if the text has been changed, and if changed, asks the user whether to save the text. If the user agrees to save the text, either SaveCmd() is called if the file was previously opened, or SaveAsCmd() if the file is new.

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

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

Cutting text, as well as copying and pasting it, undoing an action and redoing an action are performed using the corresponding methods of the DocEdit widget. The widget also has hot keys for these actions.

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();
}

Consider the menu creation code.
The Add() method is responsible for adding a drop-down menu item and a toolbar button, and it works differently depending on the arguments. If the path to the icon is specified, as is done, for example, for the "Save" command, then both the drop-down menu item and the button on the toolbar will be created. If the icon is not specified (see the "Save As..." command), then only the drop-down menu item is created. The macro THISBACK allows you to connect the appropriate method to the created menu item. The Help() method contains a string of contextual help displayed in the status bar. The Separator() method adds a separator bar to both the dropdown menu and the toolbar.

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));
}

Now consider the UppNotepad2 constructor.

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;
}

To hide platform-specific implementation details, the GUI_APP_MAIN macro is used instead of the main() function.

GUI_APP_MAIN
{
    UppNotepad2().Run();
}

Here is the result of running the code.

To add an application icon for the desktop, you need to include the *.rc file. To do this, in any text editor, write the following line:

9999 ICON "images/editor.ico"

and save it to the UppNotepad.rc file in the source directory of our application. Next, in the TheIDE file field, call the context menu and select the “Insert package directory file (s)” item and select the newly created file. This is necessary so that the *.rc file gets into the package and participates in the package assembly.

conclusions

Advantages of the framework:
- installation of the distribution is not required, it is enough to unpack the zip-archive into a suitable directory, and all the tools become available;
- extensive database of examples;
- ease of development;
- good documentation;
- the output is a single executable file that does not require any dependencies.

Flaws:
- on the site it is easier to use the search on the site than in some cases to follow broken links;
- one open window TheIDE supports only one open parent project package. This is bypassed by allowing a second and subsequent instances of TheIDE to be opened. In a sense, it's even more convenient, you can switch between windows using Alt + Tab;
- in TheIDE text editor, auto-completion does not work for keywords and variables that you start typing from a new line;
- GUI widgets can only be created in the layout, they cannot be created through the code;
- intolerance of code from machine to machine. (I wrote the editor code on one computer, I checked the code again on another machine. As a result, the rc file “fell off” and gave an error during compilation. This is due to the fact that the absolute path to the file was specified in the * .upp file. I had to reconnect.)

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.

Do you like it? Share on social networks!

Evgenii Legotckoi
  • June 15, 2020, 7:08 a.m.

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

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

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

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

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

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

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

IscanderChe
  • June 15, 2020, 7:47 a.m.
  • (edited)

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

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

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

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

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

Не смотрел.

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

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

Evgenii Legotckoi
  • June 15, 2020, 7:51 a.m.

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
Last comments
ИМ
Игорь МаксимовNov. 23, 2024, 12:51 a.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiNov. 1, 2024, 2:37 a.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 p.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 p.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 p.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 25, 2024, 3:11 a.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 7:04 p.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 p.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 p.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks