Open michael-lehn opened 2 years ago
Hello Michael, I'm glad you like FINAL CUT 😊.
First, a few remarks about your program:
FScrollView
is a meta widget that has no predefined color. You have to assign a foreground and background color to the widget. For example, you can use the method useParentWidgetColor()
here.
If you want a child widget that can draw on the padding area of the parent widget, you must explicitly enable this in the child widget by calling ignorePadding()
. The FPoint{1, 1}
is then identical to its parent widget. So you can set the geometry values for a full-size child widget from FDialog
to setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1})
.
If you use my last commit (34c3be3), you can also pass the FScrollView
mouse events of the resize-corner to the Dialog widget.
Unfortunately, I have to inform you that the widget you are trying to generate already exists under the name FTextView 😉. I use it in examples/ui.cpp
, for example.
Example:
#include <final/final.h>
using namespace finalcut;
class TextDialogWidget final : public FDialog
{
public:
explicit TextDialogWidget (FWidget* parent = nullptr)
: FDialog(parent)
{
// Fill with a few lines
for (std::size_t n{0}; n < numLines; n++)
{
scrollText << FString(FString("Line = ") << n);
}
}
private:
void initLayout() override
{
setText ("Dialog");
scrollText.ignorePadding();
scrollText.setFocus();
scrollText.setGeometry (FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
setMinimizable();
setResizeable();
setMinimumSize (FSize{17, 6});
FDialog::initLayout();
}
void adjustSize()
{
FDialog::adjustSize();
scrollText.setGeometry (FPoint{1, 2}, FSize(getWidth(), getHeight() - 1));
}
// Data members
FTextView scrollText{this};
std::size_t numLines{42};
};
int main (int argc, char* argv[])
{
FApplication app(argc, argv);
TextDialogWidget dialog(&app);
dialog.setGeometry (FPoint{28, 2}, FSize{18, 15});
app.setMainWidget(&dialog);
dialog.show();
return app.exec();
}
I just noticed that you edited your post again. Nice that you found FTextView
yourself, without a complete widget documentation.
Thanks for your fast response! And sorry for my late feedback 😬
The problem with my self-made FDialog
+ FScrollView
variant was that it "occasionally" crashed, and these crashes were more or less reproducible. But before it crashes resizing, scrolling and colouring works fine. I will try to apply your notes and also investigate further what causes the problem. It certainly requires a better understanding of things from my side. And I anyway want to understand your implementation in more detail.
Besides for learning, there actually was another reason for considering to implement some self-made text viewer. What I need is a scrollable text viewer where I can highlight a certain line, and I also want to place markers in the first column. As a dirty hack I therefore
FTextView
the private
into protected
TextView
which most of all overrides drawText
However, this is certainly not the way it should be done. Of course I could copy the FTextView
code, remove/add a few things ... but that is not much better. Do you have any suggestions for achieving something like this?
For giving some background: As the screenshot suggest I am writing a debugger for a virtual machine called ULM
(Ulm Lecture Machine). Years ago I have implemented a TUI for it with ncurses
. And I can confirm:
Users of ncurses
are cursed, literally.
Although I personally believe that programming is best learned by using the terminal only I therefore implemented a GUI with Qt
. This is certainly a nice library but Corona actually proved the importance of the terminal and TUIs:
Giving them access to a server via ssh
is therefore important. But a GUI
then requires that they have a working X-server, and even if that works it's painfully slow. So your library is a lifesaver for me. It is well-designed, compiles without problems on Linux/macOS/Solaris. Windows users can install Cygwin or can use ssh
.
Btw, the screenshot below shows the current status. The debugger can execute instruction-by-instruction and a CPU viewer shows the instruction pointer, instruction register and read/writes to general purpose registers. Thanks to FINAL CUT this was really easy to implement. And I am pretty confident that other things e.g. viewers for the memory or I/O will go similarly smooth.
Wow, I am very impressed with your screenshots. It looks very fancy.
The problem with crashes could be caused by the fact that you did not restrict the shrinking of the dialog by using the setMinimumSize()
method. Therefore, some lengths or areas may get negative values due to the difference calculation.
Another reason for a crash could be an infinite loop caused by setGeometry()
. This happens e.g. when calling setGeometry()
from adjustSize()
, because setGeometry()
has set adjust = true
by default, which results in a call to adjustSize()
.
(See the last section in chapter Areas)
Unfortunately, I have not yet come up with a better solution to prevent unwanted recursion loops.
Why avoid FScrollView for large scrolling areas:
FScrollView
uses a lot of memory because it stores the displayed data in a virtual terminal area (similar to a bitmap/pixmap) composed of FChar
elements (defined in final/ftypes.h
). FTextView
, on the other hand, keeps only the plain text information in memory.
To create a derived class of FTextView
seems to be the right way. The virtual method draw()
should be overridden in this class. This class could have additional information that allows displaying lines or text areas in different colors. For example, you could implement syntax highlighting or something similar.
I am glad that you have found a working solution to your implementation problems with FINAL CUT. It always makes me happy when my concept and interface design is well received. My intention in implementing the widget API was that it should be user-friendly and easy to learn. FINAL CUT should support developers in their work. A framework should not disturb a user too much in what he wants to do with it.
PS: ULM is a nice recursive acronym! I like it :-)
Hi Michael, I liked your idea with the colors in the text so much that I just implemented it in FTextView (dc1afbb + c5b75e5). Thank you for this great idea.
I will also generate a sample program to demonstrate functionality in one following commits.
Here is my short explanation of how to use text highlighting in FTextView:
finalcut::FTextView scrolltext{parent_widget};
scrolltext.append("Linux is like wigwam: no Gates, no Windows and Apache inside.");
finalcut::FChar fchar{};
fchar.ch[0] = L' ';
fchar.fg_color = FColor::Red;
fchar.bg_color = FColor::Yellow;
fchar.attr.byte[0] = 0;
fchar.attr.byte[1] = 0;
fchar.attr.byte[2] = 0;
fchar.attr.byte[3] = 0;
fchar.attr.bit.italic = true;
scrolltext.addHighlight(0, finalcut::FTextView::FTextHighlight{14, 6, std::move(fchar)});
finalcut::FStyle style1;
style1.setStyle (finalcut::Style::Italic);
scrolltext.addHighlight(0, finalcut::FTextView::FTextHighlight{32, 10, FColor::Green, style1});
finalcut::FStyle style2;
style2.setStyle (finalcut::Style::Underline);
scrolltext.addHighlight(0, finalcut::FTextView::FTextHighlight{47, 6, finalcut::FColorPair(FColor::White, FColor::Blue), style2});
The result looks like this:
New methods for highlighting:
void FTextView::addHighlight (std::size_t line, FTextHighlight&& hgl);
void FTextView::resetHighlight (std::size_t line);
The data class FTextHighlight:
struct FTextHighlight
{
FTextHighlight (std::size_t i, std::size_t l, FChar&& fchar) noexcept
: index{i}
, length{l}
, attributes{std::move(fchar)}
{ }
FTextHighlight (std::size_t i, std::size_t l, FColor c, const FStyle& s = FStyle()) noexcept
: index{i}
, length{l}
{
attributes.fg_color = c;
attributes.bg_color = getColorTheme()->dialog_bg;
attributes.attr = s.toFAttribute();
}
FTextHighlight (std::size_t i, std::size_t l, const FColorPair& cpair, const FStyle& s = FStyle()) noexcept
: index{i}
, length{l}
{
attributes.fg_color = cpair.getForegroundColor();
attributes.bg_color = cpair.getBackgroundColor();
attributes.attr = s.toFAttribute();
}
std::size_t index{};
std::size_t length{};
FChar attributes{};
};
UPDATE:
I have now created an example program that shows how to use text highlighting with the GNU Lesser General Public License (LGPL) text.
This is great! Thanks for the feature! Today I finally found some time for coding and used your example. This is the result (and a follow question):
The almost invisibly grey highlighted line indicates what instruction was executed and the red line indicates what instruction gets executed next. Ideally the complete line would be highlighted.
My current hack is to pad each line with spaces such that each line has (for example) 200 characters. Then I get the effect that I want. But I wonder whether there is already a feature to achieve this more elegant?
I also hope to find some time to implement a pygmentize
like syntax highlighter so that I can post some cooler screenshots ;-) Your new feature really provides lots of opportunities!
You are right. It doesn't work because the trailing whitespaces were not part of the FVTermBuffer object. I have changed this now (16b56a3) so that highlighting is also possible up to the end of the line.
constexpr auto EOL = std::numeric_limits<std::size_t>::max();
scrolltext.addHighlight(1, finalcut::FTextView::FTextHighlight{0, EOL, finalcut::FColorPair(FColor::White, FColor::Red)});
I have an idea for your project: Using jump arrows like in radare2 would visualize the program flow better.
Great! Works like a charm 👍
Also thanks for your suggestion to use jump arrows like in radare2
. That is certainly the right thing to do
Hi Michael, I just had the idea for FTextView::FTextHighlight
to provide constructors without length specification in the interface. These constructors always highlight the whole text from the given position to the end of the line. I have implemented this now (4ffd4be). A developer or you no longer need to implement this yourself.
Constructors of FTextHighlight
:
FTextHighlight (std::size_t index, std::size_t length, const FChar& fchar);
FTextHighlight (std::size_t index, const FChar& fchar);
FTextHighlight (std::size_t index, std::size_t length, const FStyle& style);
FTextHighlight (std::size_t index, const FStyle& style) ;
FTextHighlight (std::size_t index, std::size_t length, FColor fg_color, const FStyle& style = FStyle());
FTextHighlight (std::size_t index, FColor fg_color, const FStyle& style = FStyle());
FTextHighlight (std::size_t index, std::size_t length, const FColorPair& cpair, const FStyle& style = FStyle());
FTextHighlight (std::size_t index, const FColorPair& cpair, const FStyle& style = FStyle());
Again, sorry for the late response. I took some time to find time to continue with my project. It is still not finished but now I have reached a stage where it is providing some core functionality:
Here some brief demo showing the execution of a "hello, word!" program (compiled with the ULM C compiler so the code is lengthy and breakpoints are really needed), and a simple "echo" program showing how characters are read from the input buffer ...
https://user-images.githubusercontent.com/1198129/161251394-f9ff5d17-bc15-4dc4-a903-965face7bce9.mp4
However, I had to apply some hacks to finalcut. Mostly I needed some more access to internal things so I added some methods or changed in some place 'private' to 'protected'. If you are interested I can write down in more detail where and why I made some modifications. However, most of it was just done with the intension "make it work now and think about how to do it right later". I also add the result of 'git diff' this might already give some overview.
I am back from my vacation and I hope my answer is not too late for you. Thank you for the screencast video. Your implementation looks very consistent and neat.
I am very interested in where the FINAL CUT API is too restrictive or a widget cannot be used as expected. Therefore, I have analyzed your diff file with great interest.
If I see it correctly, we are talking about the widgets FLineEdit
and FTextView
. Unfortunately, all private methods in FLineEdit
have been made protected methods. So I cannot identify the intuition for this.
FTextView
lacks direct access to individual lines.
Proposal:
// Direct access to any text line
FTextViewLine& getLine (std::size_t line);
// Usage in programming
auto& text_ref = getLine(20).text;
auto& highlight_ref = getLine(20).highlight;
FTextView
does not return information about text position and size.
Proposal:
// Get the scroll position (analogous to FScrollView)
FPoint getScrollPos() const;
// Get the size of the visible text
FSize getTextVisibleSize() const;
Note: The explicit call of the method FDialog::drawTitleBar()
is not required because the call of redraw()
calls this method automatically.
dgl.setText("New title");
dgl.redraw();
This call is very economical through FINAL CUT only writes the changes to the terminal.
Your vacation is well deserved! I think most of your proposals will do the trick for me.
About FLineEdit
however, for filling the input buffer I was deriving a class from it. If a user hits some special keys (e.g. return, tab, Control-D, etc.) I want some special behaviour like adding the escaped representation (e.g. hitting return adds two characters backslash and n
). So I came up with something like that:
class IOView final : public finalcut::FDialog
{
public:
explicit IOView(finalcut::FWidget * = nullptr);
void notify();
class InputBuffer final : public finalcut::FLineEdit
{
public:
explicit InputBuffer(IOView *);
~InputBuffer();
void onKeyPress(finalcut::FKeyEvent *) override;
bool keyInput(finalcut::FKey key);
void deletePreviousCharacter();
void notify();
private:
IOView *ioView;
};
private:
using MatchList = std::vector<std::size_t>;
using StringPos = std::wstring::size_type;
void initLayout() override;
void adjustSize() override;
void onAccel(finalcut::FAccelEvent *) override;
finalcut::FTextView scrolltext{ this };
finalcut::FLabel inBufLabel{ this };
finalcut::FLabel inputLabel{ this };
InputBuffer input{ this };
finalcut::FLineEdit inBuf{ this };
};
Btw: Last week I made a YouTube video for giving some overview of my lecture. And Final Cut gets mentioned at 13:50.
Hi Michael, I have now implemented your great hints in code (083ea79).
FLineEdit
now has the following new methods:
void inputText (const FString& input);
void deletesCharacter();
void moveCursorToBegin();
void moveCursorToEnd();
void stepCursorForward (std::size_t steps = 1);
void stepCursorBackward (std::size_t steps = 1);
void setOverwriteMode (bool overwrite = true);
You can easily recreate the backspace functionality (deletePreviousCharacter()
) with:
stepCursorBackward();
deletesCharacter();
I hope that any modification of the library code is now no longer necessary to implement your application.
Many thanks for mentioning FINAL CUT in your education video.
I finally found some time to adapt my code. Thanks a lot!!
With your modifications almost everything works (and I can perfectly live with that):
stepCursorBackward();
is moving the cursor to the right. So for deletePreviousCharacter()
is used stepCursorForward()
\t
gets typed in I would like to add two characters, \\
and t
. So I tried this in my onKeyPress(FKeyEvent *ev)
method:
// ...
} else if (key == FKey::Tab) {
inputText("\\t");
stepCursorBackward(2);
ev->accept();
}
// ...
if (ev->isAccepted()) {
drawInputField();
forceTerminalUpdate();
}
This works partially. The text gets added but the input filed not redrawn. That means I can not see the added text until some other character gets typed in
Hi Michael, thank you for the feedback. When I wrote the code, I had just worked with scrollAreaForward()
and scrollAreaReverse()
. So I probably confused the text move with the cursor move. This behavior is now corrected (6571342).
On changes, inputText()
now sets the input cursor to the correct position and outputs the changed text directly in the input field.
Wow! Thank you Markus for this quick response. Everything works now like a charm! :-)
I am sudden facing a crash on the solaris machine (gcc 11.2.0, SunOS 5.11). And I can not reproduce it on any Linux and MacOS boxes.
I located the problem in codeview.cpp and could the crash with the more minimalistic test program below. It crashes in auto &highlight = scrolltext.getLine(line).highlight;
. And actually the scrolltext.getLine(line)
which is a call to std::vector::at()
. So it certainly is an out of range exception. I must be blind, but I did do not understand why ... Could you have a look at it?
#include <cinttypes>
#include <final/final.h>
#include <iostream>
class Test : public finalcut::FDialog
{
public:
// Constructor
explicit Test(finalcut::FWidget *parent = nullptr)
: finalcut::FDialog{ parent }
{
finalcut::FString instrStr;
uint64_t addr;
finalcut::FTextView::FTextViewList::size_type line;
// int line;
for (addr = 0, line = 0; addr < 42; addr += 4, ++line) {
instrStr.sprintf("0x%016" PRIX64 ", sizeof(line) = %zu, line = %zu",
addr, sizeof(line), line);
scrolltext.append(instrStr);
auto &highlight = scrolltext.getLine(line).highlight; // <- crash
}
}
void
initLayout() override
{
scrolltext.setGeometry(finalcut::FPoint{ 1, 2 },
finalcut::FSize{ getWidth(), getHeight() - 1 });
}
finalcut::FTextView scrolltext{ this };
};
int
main(int argc, char *argv[])
{
// Create the application object
finalcut::FApplication app{ argc, argv };
Test test{ &app };
test.setSize(finalcut::FSize{ 57, 20 });
test.setShadow();
// Set dialog d as main widget
finalcut::FWidget::setMainWidget(&test);
// Show and start the application
test.show();
return app.exec();
}
Hmm, that sounds very strange. I have tried to reproduce the error. Unfortunately, I have not had success.
Your system installation may be incomplete or the Solaris versions are not the same.
Thanks! Actually I am glad that you can not reproduce the error. And most of all don't see an obvious error in the code. Because I also started to believe that there is a problem with the Solaris system.
Unfortunately it a machine that is not maintained by myself. So I will abandon that system for now.
Dear Markus,
after discovering just recently your amazing library I started to play around with your examples.
I am trying to write a dialog (similar to
examples/scrollview.cpp
) that can display some text file. Of course this can be done with FTextView. But I thought implementing some simplified, self-made variant might be a good way to learn how some of the concepts of finalcut. So my problem is mainly academic and out of curiosity :DIn a first step my plan was to write just a dialog that shows a fixed number of lines, e.g
line = 1
, ...,line = 42
. This worked fine until I tried to make the dialog resizeable. My idea was that once a file is completely read into some buffer I will know the number of lines and number of columns that are needed to display it. Hence, the size of the scrollable area should at least have these "text dimensions".If I understand FScrollView::setGeometry correctly then it also changes the size of the scroll area. So after making the dialog smaller the scrollbars were gone ... My idea was that the overloaded
setGeometry
of my scrollview adjusts the scroll dimensions to the maximum of the current widget size and the "text dimensions". But this seems to be the wrong idea ...Maybe you can point me to some example or give me a hint how to manage this.
Best regards, Michael
Here the (working) variant using an instance of
FTextView
Here my attempt to achieve the same with a class derived from
FScrollView
: