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.)
Это довольно сильный недостаток. Может всё-таки возможно из кода производить редактирование?
Там наверняка можно задать относительный путь. Как бы это довольно детский недостаток для фреймворка. Должно было быть вылечено ещё на ранних стадиях.
Не смотрели, что там внутри макроса? Там обычный callback на шаблонах?
Не смотрели, как обстоит дело с поддержкой мультиязычности?
По примерам - только так. Но некоторые, сложные виджеты, реализованы как раз через код. После Qt, конечно, тёмный лес. :))
Возможно, что я не той опцией на первой машине воспользовался. Сейчас стоит как относительный путь.
Не смотрел.
Мультиязычность есть, но я подробно не разбирался.
Фигово, что подключение функций реализовано на макросах. Напоминает Qt4 с их SIGNAL и SLOT макросами.