14#include <glibmm/i18n.h>
15#include <gtkmm/builder.h>
16#include <gtkmm/button.h>
17#include <gtkmm/checkbutton.h>
18#include <gtkmm/comboboxtext.h>
19#include <gtkmm/entry.h>
20#include <gtkmm/flowbox.h>
21#include <gtkmm/grid.h>
22#include <gtkmm/label.h>
23#include <gtkmm/paned.h>
24#include <gtkmm/picture.h>
25#include <gtkmm/scale.h>
26#include <gtkmm/searchentry2.h>
27#include <gtkmm/spinbutton.h>
28#include <gtkmm/treemodel.h>
29#include <gtkmm/viewport.h>
50 auto v = std::tan(
index / (upper + 1) * M_PI / 2.0) * 500;
51 return std::round(v / 20) * 20;
55 return std::atan(gap / 500) * (upper + 1) / M_PI * 2;
60 return 30 +
static_cast<int>(
index) * 5;
63 return (tile - 30) / 5.0;
68 return value ? value :
"";
73 return strtod(val.c_str(),
nullptr);
94 _color_label(
get_widget<
Gtk::Label>(_builder,
"color-label")),
98 _stock_gallery(
get_widget<
Gtk::FlowBox>(_builder,
"flowbox")),
99 _doc_gallery(
get_widget<
Gtk::FlowBox>(_builder,
"doc-flowbox")),
100 _link_scale(
get_widget<
Gtk::Button>(_builder,
"link-scale")),
102 _combo_set(
get_widget<
Gtk::ComboBoxText>(_builder,
"pattern-combo")),
103 _search_box(
get_widget<
Gtk::SearchEntry2>(_builder,
"search")),
104 _tile_slider(
get_widget<
Gtk::Scale>(_builder,
"tile-slider")),
105 _show_names(
get_widget<
Gtk::CheckButton>(_builder,
"show-names")),
115 auto set_gap_control = [
this](){
132 _tile_slider.signal_change_value().connect([
this](Gtk::ScrollType st,
double value){
146 auto precise_gap = &get_widget<Gtk::CheckButton>(
_builder,
"gap-spin");
147 auto& mouse_friendly = get_widget<Gtk::CheckButton>(
_builder,
"gap-slider");
150 precise_gap->signal_toggled().connect([=,
this] {
151 auto precise = precise_gap->get_active();
173 _orient_slider.signal_change_value().connect([=,
this](Gtk::ScrollType st,
double value){
183 spin->signal_value_changed().connect([spin,
this](){
190 slider->set_increments(1, 1);
191 slider->set_digits(0);
192 slider->set_value(0);
193 slider->set_format_value_func([=](
double val){
194 auto upper = slider->get_adjustment()->get_upper();
197 slider->signal_change_value().connect([
this](Gtk::ScrollType st,
double value){
206 _angle_btn.signal_value_changed().connect([
this]() {
227 el->signal_value_changed().connect([el,
this]() {
238 _name_box.signal_changed().connect([
this](){
244 _search_box.signal_search_changed().connect([
this](){
255 int cat_count = pattern_categories.size();
256 for (
auto row : pattern_categories) {
261 get_widget<Gtk::Button>(
_builder,
"previous").signal_clicked().connect([
this](){
262 int previous =
_combo_set.get_active_row_number() - 1;
263 if (previous >= 0)
_combo_set.set_active(previous);
265 get_widget<Gtk::Button>(
_builder,
"next").signal_clicked().connect([cat_count,
this](){
266 auto next =
_combo_set.get_active_row_number() + 1;
267 if (next < cat_count)
_combo_set.set_active(next);
279 _stock_gallery.signal_child_activated().connect([
this](Gtk::FlowBoxChild* box){
288 _doc_gallery.signal_child_activated().connect([
this](Gtk::FlowBoxChild* box){
297 _edit_btn.signal_clicked().connect([
this](){
302 _paned.property_position().signal_changed().connect([
this](){
314 pat.
store.set_filter([
this](
const Glib::RefPtr<PatternItem>& p){
315 if (!p)
return false;
318 auto name = Glib::ustring(p->label).lowercase();
320 auto pos =
name.find(expr);
321 return pos != Glib::ustring::npos;
324 list.bind_list_store(pat.
store.get_store(), [&pat,
this](
const Glib::RefPtr<PatternItem>&
item){
325 auto const box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
331 auto const label = Gtk::make_managed<Gtk::Label>(
name);
332 label->add_css_class(
"small-font");
334 label->set_ellipsize(Pango::EllipsizeMode::END);
335 label->set_max_width_chars(0);
341 auto const cbox = Gtk::make_managed<Gtk::FlowBoxChild>();
342 cbox->set_child(*box);
343 cbox->add_css_class(
"pattern-item-box");
352 auto row = sets[
index];
367 auto const &
item = pattern ? *pattern : *empty;
394 if (
item.color.has_value()) {
413 std::sort(list.begin(), list.end(), [](Glib::RefPtr<PatternItem>& a, Glib::RefPtr<PatternItem>& b) {
414 if (!a || !b) return false;
415 if (a->label == b->label) {
416 return a->id < b->id;
418 return a->label < b->label;
434 auto scoped(_update.block());
436 _stock_gallery.unselect_all();
439 auto link_pattern = pattern;
442 if (pattern && pattern != link_pattern) {
443 _current_pattern.
id = pattern->
getId();
444 _current_pattern.link_id = link_pattern->getId();
445 _current_pattern.offset = link_pattern->getTransform().translation();
448 _current_pattern.id.clear();
449 _current_pattern.link_id.clear();
450 _current_pattern.offset = {};
455 update_widgets_from_pattern(
item);
457 auto list = update_doc_pattern_list(pattern ? pattern->
document :
nullptr);
462 for (
auto& pattern_item : list) {
463 if (pattern_item->id ==
item->
id && pattern_item->collection ==
nullptr) {
465 const double device_scale = get_scale_factor();
466 pattern_item->pix = _manager.get_image(pattern, _tile_size, _tile_size, device_scale);
467 item->pix = pattern_item->pix;
473 set_active(_doc_gallery, _doc_pattern_store,
item);
477 const double device_scale = get_scale_factor();
478 auto size = _preview.get_allocation();
480 if (
size.get_width() <= m ||
size.get_height() <= m) {
483 size.set_height(200);
486 unsigned int background = 0xffffffff;
487 auto surface = _manager.get_preview(link_pattern,
size.get_width(),
size.get_height(), background, device_scale);
490 _preview_img.set_paintable(
nullptr);
496 std::vector<Glib::RefPtr<PatternItem>> output;
497 output.reserve(list.size());
499 for (
auto pat : list) {
501 output.push_back(
item);
509std::vector<Glib::RefPtr<PatternItem>> PatternEditor::update_doc_pattern_list(
SPDocument* document) {
511 const double device_scale = get_scale_factor();
514 bool modified =
false;
515 for (
auto&&
item : patterns) {
516 auto it = _cached_items.find(
item->
id);
517 if (it !=
end(_cached_items)) {
519 if (!
item->pix)
item->pix = it->second->pix;
524 item->pix = _manager.get_image(cast<SPPattern>(document->
getObjectById(
item->
id)), _tile_size, _tile_size, device_scale);
531 update_store(patterns, _doc_gallery, _doc_pattern_store);
537 _current_document = document;
538 _cached_items.clear();
539 update_doc_pattern_list(document);
543void PatternEditor::set_stock_patterns(
const std::vector<SPPattern*>& list) {
544 const double device_scale = get_scale_factor();
547 update_store(patterns, _stock_gallery, _stock_pattern_store);
550void PatternEditor::apply_filter(
bool stock) {
551 auto scoped(_update.block());
553 _doc_pattern_store.store.apply_filter();
556 _stock_pattern_store.store.apply_filter();
560void PatternEditor::update_store(
const std::vector<Glib::RefPtr<PatternItem>>& list, Gtk::FlowBox& gallery,
PatternStore& pat) {
561 auto selected = get_active(gallery, pat);
562 if (pat.
store.assign(list)) {
564 set_active(gallery, pat, selected);
568Glib::RefPtr<PatternItem> PatternEditor::get_active(Gtk::FlowBox& gallery,
PatternStore& pat) {
569 auto empty = Glib::RefPtr<PatternItem>();
571 auto sel = gallery.get_selected_children();
572 if (sel.size() == 1) {
580std::pair<Glib::RefPtr<PatternItem>,
SPDocument*> PatternEditor::get_active() {
582 auto sel = get_active(_doc_gallery, _doc_pattern_store);
584 sel = get_active(_stock_gallery, _stock_pattern_store);
585 stock = sel ? sel->collection :
nullptr;
587 return std::make_pair(sel, stock);
590void PatternEditor::set_active(Gtk::FlowBox& gallery,
PatternStore& pat, Glib::RefPtr<PatternItem>
item) {
591 bool selected =
false;
594 if (
auto box =
dynamic_cast<Gtk::FlowBoxChild*
>(&widget)) {
596 if (pattern->id == item->id && pattern->collection == item->collection) {
597 gallery.select_child(*box);
600 for_each_descendant(*box, [&](Gtk::Widget &widget){
601 if (auto const image = dynamic_cast<Gtk::Image *>(&widget)) {
602 image->set(to_texture(item->pix));
603 return ForEachResult::_break;
605 return ForEachResult::_continue;
617 gallery.unselect_all();
621std::pair<std::string, SPDocument*> PatternEditor::get_selected() {
623 auto active = get_active();
624 auto sel = active.first;
625 auto stock_doc = active.second;
630 return std::make_pair(sel->id, stock_doc);
635 if (sel->id == _current_pattern.id.raw()) {
636 return std::make_pair(_current_pattern.link_id,
nullptr);
640 return std::make_pair(sel->id,
nullptr);
646 if (
auto first = _stock_pattern_store.store.get_store()->get_item(0)) {
647 return std::make_pair(first->id, first->collection);
651 return std::make_pair(
"",
nullptr);
655std::optional<Colors::Color> PatternEditor::get_selected_color() {
656 auto pat = get_active();
657 if (pat.first && pat.first->color.has_value()) {
658 return _color_picker.get_current_color();
664 return Geom::Point(_offset_x.get_value(), _offset_y.get_value());
670 matrix *=
Geom::Scale(_scale_x.get_value(), _scale_y.get_value());
671 matrix *=
Geom::Rotate(_angle_btn.get_value() / 180.0 * M_PI);
676bool PatternEditor::is_selected_scale_uniform() {
677 return _scale_linked;
681 auto vx = _gap_x_slider.get_value();
682 auto gap_x = _precise_gap_control ? _gap_x_spin.get_value() :
slider_to_gap(vx, _gap_x_slider.get_adjustment()->get_upper());
684 auto vy = _gap_y_slider.get_value();
685 auto gap_y = _precise_gap_control ? _gap_y_spin.get_value() :
slider_to_gap(vy, _gap_y_slider.get_adjustment()->get_upper());
690Glib::ustring PatternEditor::get_label() {
691 return _name_box.get_text();
695 auto doc =
item.collection ?
item.collection : document;
696 if (!doc)
return nullptr;
698 return cast<SPPattern>(doc->getObjectById(
item.
id));
702 auto& patterns = pat_store.
store.get_items();
703 for (
auto&
item : patterns) {
708 pat_store.
store.refresh();
711void PatternEditor::update_pattern_tiles() {
712 const double device_scale = get_scale_factor();
Cairo::RefPtr< Cairo::ImageSurface > surface
3x3 matrix representing an affine transformation.
void setTranslation(Point const &loc)
Sets the translation imparted by the Affine.
Two-dimensional point that doubles as a vector.
Coord length() const
Compute the distance from origin.
Rotation around the origin.
Gtk::TreeModelColumn< Glib::ustring > name
Gtk::TreeModelColumn< std::shared_ptr< Category > > category
Glib::RefPtr< Gtk::TreeModel > get_categories()
PatternCategoryColumns columns
Cairo::RefPtr< Cairo::Surface > get_image(SPPattern *pattern, int width, int height, double device_scale)
Glib::RefPtr< Inkscape::UI::Widget::PatternItem > get_item(SPPattern *pattern)
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
Helperclass for Gtk::Entry widgets.
Typed SVG document implementation.
SPObject * getObjectById(std::string const &id) const
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
char const * getId() const
Returns the objects current ID string.
char const * getAttribute(char const *name) const
SPPattern const * rootPattern() const
static char const *const current
std::unique_ptr< Magick::Image > image
double atan2(Point const &p)
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Glib::ustring format_classic(T const &... args)
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
std::vector< SPPattern * > sp_get_pattern_list(SPDocument *source)
Singleton class to access the preferences file in a convenient way.
Glib::RefPtr< Gdk::Texture > to_texture(Cairo::RefPtr< Cairo::Surface > const &surface)
Convert an image surface in ARGB32 format to a texture.