gtkmm : Drawing text with cairo - c++

I want to draw a simple text with Cairo in an app using Gtkmm. I want to give the font style (it can be Pango::FontDescription or Pango::Context and so on ...) directly to draw text with Cairo when the Gtk::FontButton is clicked (in other words when the signal_font_set signal is issued). In the example below, I have a Gtk::HeaderBar that contains a Gtk::FontButton that sends a Glib::RefPtr<<Pango::Context>> to the drawing class when clicked. I want something like this:
MyWindow.cpp:
#include <iostream>
#include "MyWindow.h"
MyWindow::MyWindow() {
set_default_size(1000, 1000);
set_position(Gtk::WIN_POS_CENTER);
header.set_show_close_button(true);
header.pack_start(fontButton);;
set_titlebar(header);
fontButton.signal_font_set().connect([&] {
drawingArea.select_font(fontButton.get_pango_context());
});
scrolledWindow.add(drawingArea);
add(scrolledWindow);
show_all();
}
MyDrawing.h:
#ifndef DRAWING_H
#define DRAWING_H
#include <gtkmm.h>
#include <cairo/cairo.h>
class MyDrawing : public Gtk::Layout
{
public:
MyDrawing();
~MyDrawing() override;
void select_font(Glib::RefPtr<Pango::Context> p_pangoContext);
private:
bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);
Glib::RefPtr<Pango::Context> m_pangoContext;
};
#endif // DRAWING_H
and MyDrawing.cpp:
#include <iostream>
#include <utility>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>
MyDrawing::MyDrawing() {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
}
MyDrawing::~MyDrawing() = default;
bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context> &p_context) {
auto layout = create_pango_layout("hello ");
if(m_pangoContext) {
layout->set_font_description(m_pangoContext->get_font_description());
}
else {
Pango::FontDescription fontDescription;
layout->set_font_description(fontDescription);
}
p_context->save();
p_context->set_font_size(30);
p_context->set_source_rgb(0.1, 0.1, 0.1);
p_context->move_to(40, 40);
layout->show_in_cairo_context(p_context);
p_context->restore();
return true;
}
void MyDrawing::select_font(Glib::RefPtr<Pango::Context> p_pangoContext) {
this->m_pangoContext = std::move(p_pangoContext);
queue_draw();
}
when I set up Pango::FontDescription manually like :
Pango::FontDescription fontDescription;
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_style(Pango::STYLE_ITALIC);
layout->set_font_description(fontDescription);
it works:

Here is a way that works: instead of using a Pango::Context, you can get the font name from Gtk::FontButton::get_font_name and then create a Pango::FontDescription from it. Here is a minimal example to show this:
#include <iostream>
#include <gtkmm.h>
class MyDrawing : public Gtk::Layout
{
public:
MyDrawing();
void set_font(const std::string& p_selectedFont);
private:
bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);
std::string m_selectedFont;
Glib::RefPtr<Pango::Layout> m_pangoLayout;
};
MyDrawing::MyDrawing()
{
signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
m_pangoLayout = Pango::Layout::create(get_pango_context());
m_pangoLayout->set_text(
"This text's appearance should change\n"
"when font description changes."
);
}
void MyDrawing::set_font(const std::string& p_selectedFont)
{
// Record the font selected by the user (as a string... Ugh!):
m_selectedFont = p_selectedFont;
std::cout << "Selected font: " << m_selectedFont << std::endl;
// Request a redraw:
queue_draw();
}
bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
// Create a font description from what was selected by the user earlier,
// and set it:
const Pango::FontDescription description{m_selectedFont};
m_pangoLayout->set_font_description(description);
p_context->save();
p_context->set_font_size(30);
p_context->set_source_rgb(0.1, 0.1, 0.1);
p_context->move_to(40, 40);
m_pangoLayout->show_in_cairo_context(p_context);
p_context->restore();
return true;
}
class MyWindow : public Gtk::ApplicationWindow
{
public:
MyWindow();
private:
Gtk::HeaderBar m_headerBar;
Gtk::FontButton m_fontButton;
MyDrawing m_drawingArea;
};
MyWindow::MyWindow()
{
m_headerBar.set_show_close_button(true);
m_headerBar.pack_start(m_fontButton);;
set_titlebar(m_headerBar);
m_fontButton.signal_font_set().connect(
[this]()
{
m_drawingArea.set_font(m_fontButton.get_font_name());
}
);
add(m_drawingArea);
show_all();
}
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
window.show_all();
return app->run(window);
}
I am not a Pango expert, so I don't know if there is a better way using your initial strategy. Furthermore, the string representing the font names seem to need to follow some convention. From the documentation of Pango::FontDescription::FontDescription(const Glib::ustring& font_name) (Gtkmm 3.24):
font_name must have the form "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]",
where FAMILY-LIST is a comma separated list of families optionally
terminated by a comma, STYLE_OPTIONS is a whitespace separated list of
words where each WORD describes one of style, variant, weight, or
stretch, and SIZE is an decimal number (size in points). Any one of
the options may be absent. If FAMILY-LIST is absent, then the
family_name field of the resulting font description will be
initialized to 0. If STYLE-OPTIONS is missing, then all style options
will be set to the default values. If SIZE is missing, the size in the
resulting font description will be set to 0.
so you may have to do some testing of your own to make sure this works for all fonts. In my case, all 10-15 fonts I have tested seemed to work fine.

Related

Adding a Gtk::ListStore to Gtk::TreeView and adding rows to ListStore (gtkmm)

I want to be able to add a Gtk::ListStore to a Gtk::TreeView, the treeview was implemented through glade but I have created the ListStore in C++ and added columns and rows to it.
When I run the code it shows the window and the tree_view that has been loaded from the glade file but it is empty.
mainWindow.h:
#pragma once
#include <gtkmm.h>
class MainWindow {
protected:
Gtk::Window *main_window;
Gtk::TreeView *tree_view;
Glib::RefPtr<Gtk::ListStore> list_store;
public:
void init();
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns() { add(name); add(age); }
Gtk::TreeModelColumn<Glib::ustring> name;
Gtk::TreeModelColumn<Glib::ustring> age;
};
};
mainWindow.cpp:
#include "mainWindow.h"
void MainWindow::init() {
auto app = Gtk::Application::create("org.gtkmm.example");
auto builder = Gtk::Builder::create_from_file("test.glade");
builder->get_widget("main_window", main_window);
builder->get_widget("tree_view", tree_view);
ModelColumns columns;
list_store = Gtk::ListStore::create(columns);
tree_view->set_model(list_store);
Gtk::TreeModel::Row row = *(list_store->append());
row[columns.name] = "John";
row[columns.age] = "30";
row = *(list_store->append());
row[columns.name] = "Lisa";
row[columns.age] = "27";
app->run(*main_window);
}
main.cpp:
#include "mainWindow.h"
int main() {
MainWindow m;
m.init();
}
tree_view->set_model(list_store);
tells tree_view to use list_store as its TreeModel, but it does not say what column from the list_store to render. In fact, the default behavior is that no column would be rendered.
To render both columns, you need to ask tree_view to append them.
// "Name" is column title, columns.name is data in the list_store
tree_view->append_column("Name", columns.name);
tree_view->append_column("Age", columns.age);
Gtkmm has an official tutorial book. This is the section on treeview. https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview.html.en

Custom Child Components Added But Not Visible

I am using the JUCE framework to create an audio plugin. I have created a Knob class which inherits from the Component class. My Knob class contains references to a Slider & a Label.
In my AudioProcessorEditor class, I initialize several Knob objects. However, none of the Components are visible at run-time. Am I doing something wrong or missing a step?
I have tried declaring Slider & Label objects directly inside of my AudioProcessorEditor class. When I do that, I am able to see the Slider & Label objects successfully at run-time. So I get the feeling that the issue involves my Knob class.
Knob.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
class Knob : public Component
{
public:
Knob(String, AudioProcessorEditor*);
~Knob();
void paint (Graphics&) override;
void resized() override;
String Name;
Label *KnobLabel;
Slider *KnobSlider;
AudioProcessorEditor *APE;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Knob)
};
Knob.cpp
#include "Knob.h"
Knob::Knob(String name, AudioProcessorEditor *ape)
{
// In your constructor, you should add any child components, and
// initialise any special settings that your component needs.
this->Name = name;
APE = ape;
KnobLabel = new Label(name);
KnobLabel->setColour(0x1000281, Colours::antiquewhite);
KnobLabel->setAlwaysOnTop(true);
KnobLabel->setFont(10);
KnobSlider = new Slider();
KnobSlider->setAlwaysOnTop(true);
addAndMakeVisible(KnobLabel);
addAndMakeVisible(KnobSlider);
}
void Knob::paint (Graphics& g)
{
/* This demo code just fills the component's background and
draws some placeholder text to get you started.
You should replace everything in this method with your own
drawing code..
*/
g.setColour(Colours::white);
g.fillAll();
}
void Knob::resized()
{
// This method is where you should set the bounds of any child
// components that your component contains..
auto bounds = getLocalBounds();
KnobSlider->setBounds(bounds.removeFromTop(getHeight()*8));
KnobLabel->setBounds(bounds);
}
PluginEditor.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
#include "Knob.h"
#include "Model.h"
class MoonlightAudioProcessorEditor : public AudioProcessorEditor
{
public:
MoonlightAudioProcessorEditor (MoonlightAudioProcessor&);
~MoonlightAudioProcessorEditor();
void paint (Graphics&) override;
void resized() override;
Knob *OrbitKnob;
Knob *SpaceKnob;
Knob *InertiaKnob;
void ConfigureUI();
private:
OwnedArray<Knob> Knobs;
ComponentBoundsConstrainer ResizeBounds;
ResizableCornerComponent *Resizer;
MoonlightAudioProcessor& processor;
};
PluginEditor.cpp
#include "PluginProcessor.h"
#include "PluginEditor.h"
MoonlightAudioProcessorEditor::MoonlightAudioProcessorEditor (MoonlightAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
setSize (400, 300);
ConfigureUI();
}
void MoonlightAudioProcessorEditor::ConfigureUI()
{
OrbitKnob = new Knob("Orbit", this);
SpaceKnob = new Knob("Space", this);
InertiaKnob = new Knob("Inertia", this);
Knobs.add(OrbitKnob);
Knobs.add(SpaceKnob);
Knobs.add(InertiaKnob);
for (Knob *knob : Knobs)
{
knob->KnobSlider->addListener(this);
addAndMakeVisible(knob);
knob->setAlwaysOnTop(true);
}
ResizeBounds.setSizeLimits(DEFAULT_WIDTH,
DEFAULT_HEIGHT,
MAX_WIDTH,
MAX_HEIGHT);
Resizer = new ResizableCornerComponent(this, &ResizeBounds);
addAndMakeVisible(Resizer);
setSize(processor._UIWidth, processor._UIHeight);
}
void MoonlightAudioProcessorEditor::paint (Graphics& g)
{
g.setColour (Colours::black);
g.fillAll();
}
void MoonlightAudioProcessorEditor::resized()
{
int width = getWidth();
int height = getHeight();
auto bounds = getLocalBounds();
auto graphicBounds = bounds.removeFromTop(height*.8);
auto orbitBounds = bounds.removeFromLeft(width/3);
auto spaceBounds = bounds.removeFromLeft(width/3);
if (OrbitKnob != nullptr)
{
OrbitKnob->setBounds(orbitBounds);
}
if (SpaceKnob != nullptr)
{
SpaceKnob->setBounds(spaceBounds);
}
if (InertiaKnob != nullptr)
{
InertiaKnob->setBounds(bounds);
}
if (Resizer != nullptr)
{
Resizer->setBounds(width - 16, height - 16, 16, 16);
}
processor._UIWidth = width;
processor._UIHeight = height;
}
Also, I have been using the AudioPluginHost application provided with JUCE to test my plugin. A lot of times the application will crash from a segmentation fault. Then I rebuild the plugin without changing anything and it will work.
I ended up figuring it out.
My components were being initialized after I set the size. A lot of the display logic was in the resize() function so I needed to set the size after component initialization.
You left out a call to Component::Paint(g) in Knob::Paint(Graphics& g)

QSyntaxHighlighter does not create QTextFragments

I'm working on a syntax highlighter with Qt and I wanted to add unit tests on it to check if formats are well applied.
But I don't manage to get the block divided by formats. I use QTextBlock and QTextFragment but it's not working while the doc of QTextFragment says :
A text fragment describes a piece of text that is stored with a single character format.
Here is the code in a runnable main.cpp file :
#include <QApplication>
#include <QTextEdit>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <QDebug>
class Highlighter : public QSyntaxHighlighter
{
public:
Highlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{}
protected:
void highlightBlock(const QString& text) override
{
QTextCharFormat classFormat;
classFormat.setFontWeight(QFont::Bold);
QRegularExpression pattern { "\\bclass\\b" };
QRegularExpressionMatchIterator matchIterator = pattern.globalMatch(text);
while (matchIterator.hasNext())
{
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), classFormat);
}
// ==== TESTS ==== //
qDebug() << "--------------------------------";
QTextDocument *doc = document();
QTextBlock currentBlock = doc->firstBlock();
while (currentBlock.isValid()) {
qDebug() << "BLOCK" << currentBlock.text();
QTextBlockFormat blockFormat = currentBlock.blockFormat();
QTextCharFormat charFormat = currentBlock.charFormat();
QFont font = charFormat.font();
// each QTextBlock holds multiple fragments of text, so iterate over it:
QTextBlock::iterator it;
for (it = currentBlock.begin(); !(it.atEnd()); ++it) {
QTextFragment currentFragment = it.fragment();
if (currentFragment.isValid()) {
// a text fragment also has a char format with font:
QTextCharFormat fragmentCharFormat = currentFragment.charFormat();
QFont fragmentFont = fragmentCharFormat.font();
qDebug() << "FRAGMENT" << currentFragment.text();
}
}
currentBlock = currentBlock.next();
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto *textEdit = new QTextEdit;
auto *highlighter = new Highlighter(textEdit->document());
Q_UNUSED(highlighter);
textEdit->setText("a class for test");
textEdit->show();
return a.exec();
}
And it outputs only one block "a class for test" and one format "a class for test" while the class keyword is in bold.
Thanks for your help !
Ok I found this from the documentation of QSyntaxHighlighter::setFormat :
Note that the document itself remains unmodified by the format set through this function.
The formats applied by the syntax highlighter are not stored in QTextBlock::charFormat but in the additional formats :
QVector<QTextLayout::FormatRange> formats = textEdit->textCursor().block().layout()->formats();

Custom map overlays using Marble

I'm trying to create custom overlays in Marble while following this tutorial. My code is identical to the one in the example.
Everything seems ok, but somehow the generated layer is editable and I can click it and change its size.
I'd like it to be just static on the background with no way of interacting with it.
There doesn't seem to be any obvious flag to set or function to override (so that I could just ignore all user events).
Any ideas?
Code as requested:
#include <QDebug>
#include <QFileInfo>
#include <QApplication>
#include <QImage>
#include <marble/MarbleWidget.h>
#include <marble/GeoDataDocument.h>
#include <marble/GeoDataGroundOverlay.h>
#include <marble/GeoDataTreeModel.h>
#include <marble/MarbleModel.h>
using namespace Marble;
int main(int argc, char** argv) {
QApplication app(argc,argv);
QFileInfo inputFile( app.arguments().last() );
if ( app.arguments().size() < 2 || !inputFile.exists() ) {
qWarning() << "Usage: " << app.arguments().first() << "file.png";
return 1;
}
// Create a Marble QWidget without a parent
MarbleWidget *mapWidget = new MarbleWidget();
// Load the Satellite map
mapWidget->setMapThemeId( "earth/bluemarble/bluemarble.dgml" );
// Create a bounding box from the given corner points
GeoDataLatLonBox box( 55, 48, 14.5, 6, GeoDataCoordinates::Degree );
box.setRotation( 0, GeoDataCoordinates::Degree );
// Create an overlay and assign the image to render and its bounding box to it
GeoDataGroundOverlay *overlay = new GeoDataGroundOverlay;
overlay->setLatLonBox( box );
overlay->setIcon( QImage( inputFile.absoluteFilePath() ) );
// Create a document as a container for the overlay
GeoDataDocument *document = new GeoDataDocument();
document->append( overlay );
// Add the document to MarbleWidget's tree model
mapWidget->model()->treeModel()->addDocument( document );
mapWidget->show();
return app.exec();
}
Update:
You can programatically enable/disable plugins using RenderPlugin and setVisible:
QList<RenderPlugin *> renderPluginList = marbleWidget->renderPlugins();
for (RenderPlugin *renderPlugin : renderPluginList) {
if (std::find(plugin_list.begin(), plugin_list.end(), renderPlugin->nameId()) != plugin_list.end())
{
renderPlugin->setVisible(true);
}
else
{
renderPlugin->setVisible(false);
}
}
Where plugin_list is a std::vector<QString> of plugin nameId()s.
To disable just the Annotation plugin, you could use:
QList<RenderPlugin *> renderPluginList = mapWidget->renderPlugins();
for (RenderPlugin *renderPlugin : renderPluginList) {
if (renderPlugin->nameId() == "annotation")
{
renderPlugin->setVisible(false);
}
}
In case you are still experiencing this issue, one thing to check is whether you have the AnnotationPlugin (.dll if on Windows) in the plugins/ directory. This plugin allows for moving and resizing various features on the MarbleWidget map.

Initializing a list of QColors in Qt C++

How can I initialize a static list of QColors. I'm using Qt 5.11.1
In my header file i have this:
QList<QColor> *colorList;
not sure if it's more appropriate for me to use this
QColor *colorList[15];
Then in what would i write in the CPP file... something like this?:
colorList = {
QColor(220,0,0),
QColor(250,140,0),
QColor(255,255,0),
QColor(145,210,80),
QColor(0,180,20),
...
};
I'll be eventually looping through this list of colors using the color.
Update
I'm getting an error when looping through the colors. Which I'm using the colors to define the visual color of a QPushButton I subclassed.
Here are the import bits of the code.
.h
#include <QWidget>
#include <colorswatch.h>
#include <QColorDialog>
#include <QMenu>
#include <QList>
class ColorSwatchPicker : public ColorSwatch
{
Q_OBJECT
...
private:
void init();
...
QList<QColor> *colorList;
};
.cpp
void ColorSwatchPicker::createButtons()
{
//! create color swatch menu
QWidget *colorWidget = new QWidget(this);
QGridLayout *layout = new QGridLayout(colorWidget);
layout->setSpacing(4);
layout->setContentsMargins(0,0,0,0);
// create color swatches
colorList = new QList<QColor>({
QColor(255,70,50),
QColor(230,30,100),
QColor(155,40,175),
QColor(105,60,185),
QColor(65,80,180),
...
});
// Create the pushbutton control
foreach (const QColor &c, colorList) {
auto *cs = new ColorSwatch(c, this);
cs->setFixedSize(18,18);
};
}
Define an alias (optional)
using ColorList = QList<QColor>;
Initialize the list
auto *colorList = new ColorList({
QColor(220,0,0),
QColor(250,140,0),
QColor(255,255,0),
// ...
});
Iterate over the colors in colorList
foreach (const QColor &c, *colorList) {
// do something with c
}
Note (thanks to #drescherjm and #AlbertoMiola): Alternatively you can use a ranged for instead of foreach:
for (const auto &c : *colorList) {
// do something with c
}