How to change the label positioning in a Fl_Box? - c++

I want to customize the label position inside a Fl_Box. Looking at the documentation I saw the draw_label() function here: this is a protected member of Fl_Widget, hence I derived a custom class for Fl_Box. The code is below.
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
class mBox: public Fl_Box{
public:
mBox(int X, int Y, int W, int H, const char* l=0): Fl_Box(X,Y,W,H,l){};
void drawLabel(){
label("New");
draw_label(x(),y(),100,25);
redraw_label();
};
};
void action(Fl_Widget* w, void* data){
mBox* B = (mBox*) data;
B -> drawLabel(); }
int main(int argc, char **argv) {
Fl_Window* G_win;
G_win = new Fl_Window(180,100,"The font test");
mBox* A = new mBox(10,10,110,50,"The font TEST.");
A -> box(FL_UP_BOX);
Fl_Button* b = new Fl_Button(10,70,100,25,"Test");
b -> callback(action,A);
G_win->show();
return(Fl::run()); }
When the button is pressed, I expect that the label in the box changes to "New" in the new bounding box whose left corner is at position x(), y() and its width and height are 110 and 50 (the dimension of the box), respectively. I call the redraw_label() function to force the drawing with the new bounding box. For what I understood, the new label should be in the top left corner of the box.
But what actually happens is that the new label is indeed "New", but its position is not changed. What am I missing here?
This question is a follow-up of this previous question: I am trying to understand how to change the position of the label with default font and then try to customize the position using non standard fonts.

The draw_label() method is intended to be used in the draw() method. If you want to trigger a draw based on a callback, this is a modification of your code:
#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/fl_draw.H>
class mBox : public Fl_Box {
bool triggered = false;
public:
mBox(int X, int Y, int W, int H, const char* l = 0)
: Fl_Box(X, Y, W, H, l) {};
void drawLabel() {
triggered = true;
label("New");
};
void draw() override {
Fl_Box::draw();
if (triggered) {
fl_draw_box(box(), x(), y(), w(), h(), FL_BACKGROUND_COLOR);
draw_label(x(), y(), 100, 25);
}
}
};
void action(Fl_Widget* w, void* data) {
mBox* B = (mBox*)data;
B->drawLabel();
}
int main(int argc, char** argv) {
Fl_Window* G_win;
G_win = new Fl_Window(180, 100, "The font test");
mBox* A = new mBox(10, 10, 110, 50, "The font TEST.");
A->box(FL_UP_BOX);
Fl_Button* b = new Fl_Button(10, 70, 100, 25, "Test");
b->callback(action, A);
G_win->show();
return (Fl::run());
}

Although the previous answer has been accepted I'd like to share some thoughts and code to answer the text alignment question in general. If you want to align text precisely I suggest to use fl_draw() directly to draw the box label text aligned as required rather than using draw_label(). The following code does this in the draw() method of the derived class:
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/fl_draw.H>
class mBox : public Fl_Box {
public:
mBox(int X, int Y, int W, int H, const char *l = 0)
: Fl_Box(X, Y, W, H, l){};
void draw() {
draw_box();
// arbitrary text position, change this as you need
int xo = x() + 4;
int yo = y() + h() * 2 / 3;
// measure the text extents
int dx = 0, dy = 0, tw = 0, th = 0;
fl_font(labelfont(), labelsize());
fl_text_extents(label(), dx, dy, tw, th);
// draw the green base line
fl_color(FL_GREEN);
fl_xyline(xo, yo, xo + w() - 8);
// draw the text aligned to the green base line
fl_color(labelcolor());
fl_draw(label(), x() + (w()-tw)/2, yo);
}
};
void action(Fl_Widget *w, void *data) {
mBox *B = (mBox *)data;
B->label("New");
B->redraw();
}
int main(int argc, char **argv) {
Fl_Window *G_win;
G_win = new Fl_Window(510, 150, "The font test");
mBox *A1 = new mBox(10, 10, 240, 40, "Quick fox jumps over lazy dog.");
A1->box(FL_UP_BOX);
Fl_Box *A2 = new Fl_Box(260, 10, 240, 40, "Quick fox jumps over lazy dog.");
A2->box(FL_UP_BOX);
Fl_Button *b = new Fl_Button(10, 110, 100, 25, "Test");
b->callback(action, A1);
G_win->show();
return (Fl::run());
}
I modified the callback to set the label text and call redraw(). Everything else is done in draw().
I also added a standard Fl_Box widget to show the difference.

Related

GTKmm popover menu items not highlighting when used with treeview

Good afternoon,
I'm trying to integrate a popover context menu to a treeview widget in GTKmm 4.
I've been successful in getting the menu to be displayed and for the respective actions to be called when clicking on the context menu options, however, I'm finding that the menu items are not being highlighted when the mouse hovers over them.
A GIF showing what I'm seeing is here:
If however, using the same code, I attach the menu and action group to another widget (such as a button or the window itself), all works as expected and the options are highlighted correctly.
Below is code for a minimal reproducible example.
Could someone help as I'm going round in circles with this??
#include <gtkmm.h>
class Window : public Gtk::Window {
public:
Window() {
list_store_ = Gtk::ListStore::create(model_);
auto row = *(list_store_->append());
row[model_.id] = 1;
row[model_.name] = "Example 1";
row = *(list_store_->append());
row[model_.id] = 2;
row[model_.name] = "Example 2";
treeview_.set_hexpand(true);
treeview_.set_vexpand(true);
treeview_.set_model(list_store_);
treeview_.append_column("ID", model_.id);
treeview_.append_column("Name", model_.name);
Glib::RefPtr<Gio::Menu> gmenu = Gio::Menu::create();
gmenu->append("_Edit", "popup.edit");
gmenu->append("_Remove", "popup.remove");
menu_.set_parent(treeview_);
menu_.set_menu_model(gmenu);
menu_.set_has_arrow(false);
Glib::RefPtr<Gio::SimpleActionGroup> action_group = Gio::SimpleActionGroup::create();
action_group->add_action("edit", sigc::mem_fun(*this, &Window::on_popup_edit));
action_group->add_action("remove", sigc::mem_fun(*this, &Window::on_popup_remove));
treeview_.insert_action_group("popup", action_group);
Glib::RefPtr<Gtk::GestureClick> gesture = Gtk::GestureClick::create();
gesture->set_button(GDK_BUTTON_SECONDARY);
gesture->signal_pressed().connect(sigc::mem_fun(*this, &Window::on_popup_button_pressed));
treeview_.add_controller(gesture);
set_child(treeview_);
}
~Window() override {
}
private:
class ExampleModel : public Gtk::TreeModel::ColumnRecord {
public:
ExampleModel() {
add(id);
add(name);
}
Gtk::TreeModelColumn<int> id;
Gtk::TreeModelColumn<Glib::ustring> name;
};
void on_popup_button_pressed(int, double x, double y) {
int cx, cy;
treeview_.convert_widget_to_bin_window_coords(x, y, cx, cy);
Gtk::TreeModel::Path path;
treeview_.get_path_at_pos(cx, cy, path);
if (!path) {
return;
}
const Gdk::Rectangle rect(x, y, 1, 1);
menu_.set_pointing_to(rect);
menu_.popup();
}
void on_popup_edit() { /* Implementation here */ }
void on_popup_remove() { /* Implementation here */ }
Gtk::TreeView treeview_;
ExampleModel model_;
Glib::RefPtr<Gtk::ListStore> list_store_;
Gtk::PopoverMenu menu_;
};
int main(int argc, char** argv) {
auto app = Gtk::Application::create("com.example.treeview");
return app->make_window_and_run<Window>(argc, argv);
}
Okay, so I recently had time to come back to this project.
This time I thought I'd attempt to re-write it in Rust using the gtk4-rs crate and see if the same happens and it does!
I've managed to work around this issue by wrapping the TreeView inside of a ScrolledWindow and then setting up the menu against the ScrolledView instead of the TreeView.
Inside the on_popup_button_pressed function, I'm still able to determine the path/item that was clicked.
Updated code (untested though - as the project is now written in Rust instead) is below:
#include <gtkmm.h>
class Window : public Gtk::Window {
public:
Window() {
list_store_ = Gtk::ListStore::create(model_);
auto row = *(list_store_->append());
row[model_.id] = 1;
row[model_.name] = "Example 1";
row = *(list_store_->append());
row[model_.id] = 2;
row[model_.name] = "Example 2";
treeview_.set_hexpand(true);
treeview_.set_vexpand(true);
treeview_.set_model(list_store_);
treeview_.append_column("ID", model_.id);
treeview_.append_column("Name", model_.name);
scrolled_window_.set_child(treeview_);
Glib::RefPtr<Gio::Menu> gmenu = Gio::Menu::create();
gmenu->append("_Edit", "popup.edit");
gmenu->append("_Remove", "popup.remove");
menu_.set_parent(scrolled_window_);
menu_.set_menu_model(gmenu);
menu_.set_has_arrow(false);
Glib::RefPtr<Gio::SimpleActionGroup> action_group = Gio::SimpleActionGroup::create();
action_group->add_action("edit", sigc::mem_fun(*this, &Window::on_popup_edit));
action_group->add_action("remove", sigc::mem_fun(*this, &Window::on_popup_remove));
scrolled_window.insert_action_group("popup", action_group);
Glib::RefPtr<Gtk::GestureClick> gesture = Gtk::GestureClick::create();
gesture->set_button(GDK_BUTTON_SECONDARY);
gesture->signal_pressed().connect(sigc::mem_fun(*this, &Window::on_popup_button_pressed));
scrolled_window_.add_controller(gesture);
set_child(scrolled_window_);
}
~Window() override {
}
private:
class ExampleModel : public Gtk::TreeModel::ColumnRecord {
public:
ExampleModel() {
add(id);
add(name);
}
Gtk::TreeModelColumn<int> id;
Gtk::TreeModelColumn<Glib::ustring> name;
};
void on_popup_button_pressed(int, double x, double y) {
int cx, cy;
treeview_.convert_widget_to_bin_window_coords(x, y, cx, cy);
Gtk::TreeModel::Path path;
treeview_.get_path_at_pos(cx, cy, path);
if (!path) {
return;
}
const Gdk::Rectangle rect(x, y, 1, 1);
menu_.set_pointing_to(rect);
menu_.popup();
}
void on_popup_edit() { /* Implementation here */ }
void on_popup_remove() { /* Implementation here */ }
Gtk::TreeView treeview_;
Gtk::ScrolledWindow scrolled_window_;
ExampleModel model_;
Glib::RefPtr<Gtk::ListStore> list_store_;
Gtk::PopoverMenu menu_;
};
int main(int argc, char** argv) {
auto app = Gtk::Application::create("com.example.treeview");
return app->make_window_and_run<Window>(argc, argv);
}

FLTK: Clearing a graph when drawing another

I wrote a simple FLTK program to draw a circle when clicking on the "Draw Circle" button and to draw a line when clicking on the "Draw Line" button. I supposed to have only one graph. But I got two graphs in the panel. I want only one showing and the other disappearing. The following is the code:
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Box.H>
using namespace std;
int flag = 0;
class Drawing : public Fl_Box {
void draw() {
fl_color(255, 0, 0);
int x, y, x1, y1;
if (flag == 1) {
double radius = 100;
x = (int)(w() / 2);
y = (int)(h() / 2);
fl_circle(x, y, radius);
}
else if (flag == -1) {
x = (int)(w() / 4);
y = (int)(h() / 4);
x1 = (int)(w() *3/ 4);
y1 = (int)(h() *3/ 4);
fl_line(x, y, x1, y1);
}
}
public:
Drawing(int X, int Y, int W, int H) : Fl_Box(X, Y, W, H) {}
};
Drawing* d;
void circle_cb(Fl_Widget*, void*) {
flag = 1;
fl_overlay_clear();
d->redraw();
} // end sumbit_cb
void line_cb(Fl_Widget*, void*) {
flag = -1;
fl_overlay_clear();
d->redraw();
} // end clear_cb
int main(int argc, char** argv) {
Fl_Window* window = new Fl_Window(600, 550); // create a window, originally(400,400)
Drawing dr(0, 0, 600, 600);
d = &dr;
Fl_Button *b, *c;
b = new Fl_Button(150, 80, 100, 25, "&Draw Circle");
b->callback(circle_cb);
c = new Fl_Button(350, 80, 100, 25, "&Draw Line");
c->callback(line_cb);
window->end(); //show the window
window->show(argc, argv);
return Fl::run();
}
I have used fl_overlay_clear() to clear graph. However it is not working. Any help will be appreciated.
There are several issues that need to be fixed in your program, but first of all using the draw() method as you did is basically correct. However, using fl_overlay_clear(); is useless, you can remove it.
My solution: your widget doesn't have a solid background (boxtype), i.e. your draw method draws over the background over and over again w/o clearing it. There are several ways to solve this, but if you want to learn what happens, try this first: add window->resizable(window); before window->show(argc, argv);, run the program again and resize the window. You'll notice that the previous drawing disappears and only one drawing stays. That's because the background is cleared when you resize the widget.
Next step: add a solid boxtype:
d = &dr;
d->box(FL_DOWN_BOX);
and add Fl_Box::draw(); right at the beginning of your draw() method.
If you do that you may notice that your button(s) disappear when you click one of them - because your buttons are inside the area of your Drawing. The last thing(s) I fixed was to correct the coordinates of buttons and to enlarge the window (it was too small anyway to cover the entire Drawing). Here's my complete result:
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Box.H>
using namespace std;
int flag = 0;
class Drawing : public Fl_Box {
void draw() {
Fl_Box::draw();
fl_color(255, 0, 0);
int x, y, x1, y1;
if (flag == 1) {
double radius = 100;
x = (int)(w() / 2);
y = (int)(h() / 2);
fl_circle(x, y, radius);
} else if (flag == -1) {
x = (int)(w() / 4);
y = (int)(h() / 4);
x1 = (int)(w() * 3 / 4);
y1 = (int)(h() * 3 / 4);
fl_line(x, y, x1, y1);
}
}
public:
Drawing(int X, int Y, int W, int H)
: Fl_Box(X, Y, W, H) {}
};
Drawing *d;
void circle_cb(Fl_Widget *, void *) {
flag = 1;
// fl_overlay_clear(); // not useful
d->redraw();
} // end sumbit_cb
void line_cb(Fl_Widget *, void *) {
flag = -1;
// fl_overlay_clear(); // not useful
d->redraw();
} // end clear_cb
int main(int argc, char **argv) {
Fl_Window *window = new Fl_Window(600, 660); // create a window, originally(400,400)
Drawing dr(0, 60, 600, 600); // FIXED
d = &dr;
d->box(FL_DOWN_BOX); // ADDED
Fl_Button *b, *c;
b = new Fl_Button(150, 20, 100, 25, "&Draw Circle"); // FIXED
b->callback(circle_cb);
c = new Fl_Button(350, 20, 100, 25, "&Draw Line"); // FIXED
c->callback(line_cb);
window->end(); // show the window
window->resizable(window); // ADDED
window->show(argc, argv);
return Fl::run();
}
I believe this does what you want.
PS: the official FLTK support forum can be found on our website https://www.fltk.org/ and the direct link to the user forum (Google Groups) is https://groups.google.com/g/fltkgeneral
Just a quick addition to what Albrecht put so perfectly: FLTK drawing coordinates are relative to the window, not relative to the widget. You probably want to offset your drawing by the x() and y() coordinates of your widget.
In your handle() methods line_cb() , circle_cb() should call window()->make_current() and then fl_overlay_rect() after FL_DRAG events, and should call fl_overlay_clear() after a FL_RELEASE event. Refer for more details

FLTK flickering animation

I'm trying to make a simple animation using FLTK(a circle with increasing and decreasing radius). I've managed to write a simple program that seems to work, but the animation flickers. The circle disappears for a couple of milliseconds and then gets back. I've changed Fl_Window class to Fl_Double_Window, but that didn't fix this problem.
class Painting : public Fl_Widget {
public:
Painting(int x, int y, int w, int h) : Fl_Widget(x, y, w, h, 0) {}
private:
void draw()
{
static double inc = 0;
inc += 0.2;
double radius = 50 + 10*sin(inc);
fl_begin_polygon();
fl_arc(100, 100, radius, 0, 360);
fl_end_polygon();
}
};
void redraw_cb(void *data)
{
Fl_Widget *w = (Fl_Widget*)data;
w->redraw();
Fl::repeat_timeout(0.01, redraw_cb, data);
}
int main(int argc, char **argv)
{
Fl_Double_Window *win = new Fl_Double_Window(1000, 500, "hello");
Painting *painting = new Painting(0, 0, 1000, 500);
Fl::add_timeout(1, redraw_cb, painting);
Fl::visual(FL_DOUBLE|FL_INDEX);
win->resizable(painting);
win->end();
win->show();
return Fl::run();
}

C++ FLTK FL_INPUT detect when enter pressed then add text to FL_TEXT_DISPLAY

I want to detect when enter is pressed in FL_INPUT and add its text to FL_Text_Display using C++
please help me I dont know what to do this is all I got
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Input.H>
using namespace std;
int main(int argc,char **argv) {
Fl_Window win(320,240,"BIC");
Fl_Text_Buffer txtbuf;
win.begin();
Fl_Text_Display ted(2,2,320-2,240-2-32-2);
Fl_Input inp(2,240-2-32,320-2,32);
win.end();
ted.buffer(txtbuf);
win.resizable(ted);
win.show();
return Fl::run();
}
You would have to inherit Fl_Input and override the virtual int handle(int) method.
class MyInput : public Fl_Input {
public:
MyInput(int x, int y, int w, int h, const char* title=0) : Fl_Input(x, y, w, h, title) {}
virtual int handle(int e) override {
switch(e) {
case FL_ENTER: foo(); return 1;
default: return 0;
}
}
};
Deriving a new class is possible but the OP's question is easier to solve with a callback. That's what callbacks are designed for. Modified example code follows:
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Input.H>
// input callback (called when ENTER is pressed)
void input_cb(Fl_Widget *w, void *v) {
Fl_Input *inp = (Fl_Input *)w;
Fl_Text_Display *ted = (Fl_Text_Display *)v;
Fl_Text_Buffer *tbuf = ted->buffer();
tbuf->append(inp->value());
tbuf->append("\n"); // append newline (optional)
ted->insert_position(tbuf->length());
ted->show_insert_position();
inp->value("");
inp->take_focus();
}
int main(int argc, char **argv) {
Fl_Window win(320, 240, "BIC");
Fl_Text_Buffer txtbuf;
win.begin();
Fl_Text_Display ted(2, 2, 320 - 2, 240 - 2 - 32 - 2);
Fl_Input inp(2, 240 - 2 - 32, 320 - 2, 32);
win.end();
inp.callback(input_cb, (void *)&ted); // set callback
inp.when(FL_WHEN_ENTER_KEY); // when ENTER is pressed
ted.buffer(txtbuf);
txtbuf.append("first line\n");
win.resizable(ted);
win.show();
return Fl::run();
}
There's a little more code involved to position to the end of the buffer to display new text when entered and to set the input focus back to the input widget, but I tried to make the example complete. There's no error handling though.

FLTK: how to make widgets resizable by dragging?

In FLTK, is there a way to let users resize widgets during runtime by dragging the borders of the widget's box? I mean, for instance, to resize a Fl_Text_Display or a Fl_Box or a Fl_Pack the same way we usually to that for a window in any OS?
I have gone through all the demos that come with FLTK and I have done quite a bit of searching, but only found examples of resizing via code or for user to resize via button-clicking. I could not find anything that points me to the right direction of how to make the borders of an widget become draggable in order to make widgets resizable by dragging.
You can explore Fl_Group and Fl_Tile resizing capabilities, perhaps using them as wrappers. Unfortunately FLTK doesn't support this out of box for every widget, so you have to make it on your own. If you only need to make a couple of custom widgets, the code below gives you the general idea to start off:
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <cmath>
class Resizeable : public Fl_Box
{
public:
Resizeable(int X, int Y, int W, int H)
: Fl_Box(X, Y, W, H, "Resize Me") {}
private:
bool can_resize;
bool is_on_right_bottom_corner;
void draw()
{
Fl_Box::draw();
fl_rect(x(), y(), w(), h(), FL_RED);
int bottom_right_x = w() + x();
int bottom_right_y = h() + y();
fl_polygon(bottom_right_x - 6, bottom_right_y - 1,
bottom_right_x - 1, bottom_right_y - 6,
bottom_right_x -1, bottom_right_y - 1);
}
int handle(int event)
{
switch (event) {
case FL_PUSH: {
can_resize = is_on_right_bottom_corner;
return 1;
}
case FL_RELEASE:
can_resize = false;
return 1;
case FL_DRAG: {
if (can_resize) {
int X = Fl::event_x();
int Y = Fl::event_y();
int W = X > x() + 1 ? X - x() : w();
int H = Y > y() + 1 ? Y - y() : h();
size(W, H);
parent()->redraw();
}
return 1;
}
case FL_MOVE: {
int dist_right_border = std::abs(x() + w() - Fl::event_x());
int dist_bottom_border = std::abs(y() + h() - Fl::event_y());
is_on_right_bottom_corner = (dist_right_border < 10 && dist_bottom_border < 10);
window()->cursor(is_on_right_bottom_corner ? FL_CURSOR_SE : FL_CURSOR_DEFAULT);
return 1;
}
case FL_ENTER:
return 1;
case FL_LEAVE:
window()->cursor(FL_CURSOR_DEFAULT);
return 1;
}
return 0;
}
};
int main()
{
Fl_Double_Window win(300, 300, "Resize Example");
Resizeable res(50, 50, 100, 40);
win.show();
return Fl::run();
}