16#include <glibmm/convert.h>
17#include <glibmm/fileutils.h>
18#include <glibmm/i18n.h>
19#include <glibmm/main.h>
20#include <glibmm/miscutils.h>
21#include <gtkmm/builder.h>
22#include <gtkmm/button.h>
23#include <gtkmm/error.h>
24#include <gtkmm/filedialog.h>
25#include <gtkmm/flowbox.h>
26#include <gtkmm/messagedialog.h>
27#include <gtkmm/progressbar.h>
28#include <gtkmm/widget.h>
31#include <sigc++/scoped_connection.h>
64 , _isolate_item{isolate_item}
66 init(std::move(drawing));
76 init(std::move(drawing));
87 Glib::ustring
label =
"no-name";
105 set_tooltip_text(
label);
119 _grid.set_row_spacing(5);
120 _grid.set_column_spacing(5);
121 _grid.set_valign(Gtk::Align::CENTER);
133 _option.set_valign(Gtk::Align::END);
135 _preview.set_name(
"export_preview_batch");
139 _preview.set_halign(Gtk::Align::CENTER);
140 _preview.set_valign(Gtk::Align::CENTER);
142 _label.set_width_chars(10);
143 _label.set_ellipsize(Pango::EllipsizeMode::END);
144 _label.set_halign(Gtk::Align::CENTER);
146 set_valign(Gtk::Align::START);
147 set_halign(Gtk::Align::START);
149 set_focusable(
false);
151 _selector.signal_toggled().connect([
this]() {
154 _option.signal_toggled().connect([
this]() {
169 auto box =
dynamic_cast<Gtk::FlowBox *
>(get_parent());
170 if (box && selected != is_selected()) {
172 box->select_child(*
this);
174 box->unselect_child(*
this);
184 if (
auto parent =
dynamic_cast<Gtk::FlowBox *
>(get_parent()))
188 }
else if (
_option.get_visible()) {
199 _option.set_visible(
mode == Gtk::SelectionMode::SINGLE);
207 auto parent =
dynamic_cast<Gtk::FlowBox *
>(get_parent());
216 }
else if (
_option.get_visible()) {
217 _option.set_active(is_selected());
225 auto group =
item->get_radio_group();
246 auto remove_grid_child = [&] (Gtk::Widget &widget) {
247 if (widget.get_parent() == &
_grid) {
248 _grid.remove(widget);
253 remove_grid_child(
_label);
257 _selector.set_valign(Gtk::Align::BASELINE);
259 _label.set_max_width_chars(-1);
266 _label.set_max_width_chars(18);
304 for (
auto it =
items.begin(); it !=
items.end();) {
305 if (!objects.contains(it->first)) {
306 container.remove(*it->second);
307 it =
items.erase(it);
309 it->second->setIsolateItem(isolate_items);
317 std::set<SPPage *, SPPage::PageIndexOrder> pages;
319 for (
auto &[
id, obj] : objects) {
320 if (
auto page = cast<SPPage>(obj)) {
326 auto item = cast<SPItem>(obj);
335 container.remove(*
items[
id]);
339 container.insert(*
items[
id], -1);
340 items[id]->set_selected(
true);
343 for (
auto &
page : pages) {
344 if (
auto id =
page->getId()) {
346 container.remove(*
items[
id]);
349 container.insert(*
items[
id], -1);
350 items[id]->set_selected(
true);
355 g_assert(
items.size() == objects.size());
356 g_assert(container.get_children().size() ==
items.size());
399 if (!(flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
420 Glib::ustring pref_key_name =
prefs->
getString(
"/dialogs/export/batchexportarea/value");
480 std::map<std::string, SPObject*> objects;
482 bool isolate =
false;
483 char *num_str =
nullptr;
493 num_str = g_strdup_printf(ngettext(
"%d Item",
"%d Items", objects.size()), (
int)objects.size());
500 if (layer->geometricBounds() && layer->getId()) {
501 objects[layer->getId()] = layer;
504 num_str = g_strdup_printf(ngettext(
"%d Layer",
"%d Layers", objects.size()), (
int)objects.size());
509 if (
auto id =
page->getId()) {
513 num_str = g_strdup_printf(ngettext(
"%d Page",
"%d Pages", objects.size()), (
int)objects.size());
539 std::vector<SPItem const *> selected;
542 selected = {sels.begin(), sels.end()};
581 if (!path.empty() && Glib::path_is_absolute(Glib::filename_from_utf8(path))) {
582 return Gio::File::create_for_parse_name(path);
594 auto doc_path = Glib::path_get_dirname(doc_filename);
597 return Gio::File::create_for_path(Glib::canonicalize_filename(path.raw(), doc_path));
599 return Gio::File::create_for_path(doc_path);
611 Glib::ustring path_utf8 =
"";
612 if (path.has_value()) {
613 path_utf8 = path.value()->get_parse_name();
623 auto doc_path = Glib::path_get_dirname(doc_filename);
625 path_label = path_utf8;
645 }
else if (!fallback)
648 std::string
name = Glib::path_get_basename(doc_filename);
676 auto dialog = Gtk::FileDialog::create();
677 dialog->select_folder(
dynamic_cast<Gtk::Window &
>(*get_root()), sigc::track_object([&dialog = *dialog,
this] (
auto &
result) {
679 if (
auto old_file = dialog.select_folder_finish(
result)) {
680 setBatchPath(old_file);
683 }
catch (Gtk::DialogError
const &) {
726 if (!path.has_value()) {
731 if (path.value()->query_file_type() != Gio::FileType::DIRECTORY) {
733 if (path.value()->query_exists()) {
734 auto dialog = Gtk::MessageDialog(*window, _(
"Can not save to a directory that is actually a file."),
true, Gtk::MessageType::ERROR, Gtk::ButtonsType::OK);
738 Glib::ustring message = g_markup_printf_escaped(
739 _(
"<span weight=\"bold\" size=\"larger\">Directory \"%s\" doesn't exist. Create it now?</span>"),
740 path.value()->get_parse_name().c_str());
742 auto dialog = Gtk::MessageDialog(*window, message,
true, Gtk::MessageType::WARNING, Gtk::ButtonsType::YES_NO);
746 path.value()->dup()->make_directory_with_parents();
755 std::vector<Glib::ustring> suffixs;
756 std::vector<Inkscape::Extension::Output *> extensions;
757 std::vector<double> dpis;
758 for (
int i = 0; i < num_rows; i++) {
767 std::vector<SPItem const *> selected_items(sels.begin(), sels.end());
770 for (
int j = 0; j < num_rows && !
interrupted; j++) {
776 if (!ext || ext->deactivated()) {
784 auto &batchItem = i->second;
785 if (!batchItem->is_selected()) {
791 bool isolate_item = batchItem->isolateItem();
793 std::vector<SPItem const *> show_only;
802 for (
auto &sel_item : selected_items) {
805 show_only.emplace_back(sel_item);
808 if (show_only.empty())
810 }
else if (isolate_item) {
812 show_only.emplace_back(
item);
815 area =
page->getDocumentRect();
817 show_only = selected_items;
822 std::string
id = Glib::filename_from_utf8(batchItem->getLabel());
827 std::string item_name =
name;
829 std::string::value_type last_char =
name.at(
name.length() - 1);
830 if (last_char !=
'/' && last_char !=
'\\') {
834 if (
id.at(0) ==
'#' && batchItem->getItem() && !batchItem->getItem()->label()) {
835 item_name +=
id.substr(1);
840 if (!suffix.empty()) {
841 if (ext->is_raster()) {
843 suffix = std::regex_replace(suffix.c_str(), std::regex(
"\\{dpi\\}"), std::to_string((
int)dpi));
845 item_name +=
"_" + suffix;
848 if (item_name.empty()) {
849 g_error(
"Empty item name in batch export, refusing to export.");
855 std::string item_filename = Glib::build_filename(path.value()->get_path(), item_name);
861 item_filename += ext->get_extension();
863 auto item_file = Gio::File::create_for_path(item_filename);
864 auto item_filename_utf8 = Glib::filename_to_utf8(item_filename);
868 double progress = (((double)count /
num) + j) / num_rows;
871 setExporting(
true, Glib::ustring::compose(_(
"Exporting %1"), item_filename_label),
872 Glib::ustring::compose(_(
"Format %1, Selection %2"), j + 1, count));
874 if (ext->is_raster()) {
875 unsigned long int width = (int)(area.
width() * dpi / DPI_BASE + 0.5);
876 unsigned long int height = (int)(area.
height() * dpi / DPI_BASE + 0.5);
880 }
else if (
page || !show_only.empty()) {
897 Glib::ustring pref_key_name =
prefs->
getString(
"/dialogs/export/batchexportarea/value");
899 if (pref_key_name ==
name) {
922 prefs->
setString(
"/dialogs/export/batchexportarea/value", pref_key_name);
931 _prog.set_text(text);
932 _prog.set_fraction(0.0);
939 _prog.set_fraction(0.0);
947 bi->_prog.set_fraction(value);
948 auto main_context = Glib::MainContext::get_default();
949 main_context->iteration(
false);
950 return !bi->interrupted;
998 }, Glib::PRIORITY_HIGH);
1006 refresh_conn = Glib::signal_idle().connect([
this, rename_file] {
1010 }, Glib::PRIORITY_HIGH);
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
Axis aligned, non-empty rectangle.
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
std::list< SPItem * > getAllLayers()
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
SPItemRange items()
Returns a range of selected SPItems.
bool isEmpty()
Returns true if no items are selected.
sigc::connection connectPagesChanged(const sigc::slot< void()> &slot)
const std::vector< SPPage * > & getPages() const
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
The set of selected SPObjects for a given document and layer model.
Gtk::Button & path_chooser
Gtk::CheckButton & overwrite
std::map< std::string, std::unique_ptr< BatchItem > > current_items
sigc::scoped_connection export_conn
void setBatchName(Glib::ustring const &name)
std::optional< Glib::RefPtr< Gio::File const > > export_path
Filesystem path to export folder.
Gtk::CheckButton & show_preview
Inkscape::Preferences * prefs
void loadExportHints(bool rename_file)
Glib::ustring getBatchName(bool fallback) const
Get the last used batch base name for the document:
sigc::scoped_connection cancel_conn
selection_mode current_key
Gtk::Label & num_elements
void onAreaTypeToggle(selection_mode key)
sigc::scoped_connection refresh_conn
std::map< selection_mode, Glib::ustring > selection_names
void setDefaultSelectionMode()
void setDesktop(SPDesktop *desktop)
Inkscape::UI::Widget::ColorPicker & _background_color
void setExporting(bool exporting, Glib::ustring const &text="", Glib::ustring const &test_batch="")
std::optional< Glib::RefPtr< Gio::File const > > getBatchPath() const
Get the currently selected batch path.
Gtk::ProgressBar & _prog_batch
Gtk::CheckButton & hide_all
void queueRefresh(bool rename_file=false)
Gtk::FlowBox & preview_container
sigc::scoped_connection _pages_changed_connection
std::shared_ptr< PreviewDrawing > _preview_drawing
void setBatchPath(std::optional< Glib::RefPtr< Gio::File const > > path)
Set batch export folder.
void selectionModified(Inkscape::Selection *selection, guint flags)
static unsigned int onProgressCallback(float value, void *)
void selectionChanged(Inkscape::Selection *selection)
std::map< selection_mode, Gtk::ToggleButton * > selection_buttons
void setDocument(SPDocument *document)
BatchExport(BaseObjectType *cobject, const Glib::RefPtr< Gtk::Builder > &builder)
sigc::scoped_connection refresh_items_conn
std::optional< Glib::RefPtr< Gio::File const > > getPreviousBatchPath() const
Get the last used batch path for the document:
sigc::scoped_connection _object_modified_conn
void setIsolateItem(bool isolate)
Set "Export selected only".
static void syncItems(BatchItems &items, std::map< std::string, SPObject * > const &objects, Gtk::FlowBox &container, std::shared_ptr< PreviewDrawing > preview, bool isolate_items)
Add and remove batch items and their previews carefully and insert new ones into the container FlowBo...
Gtk::CheckButton _selector
void on_mode_changed(Gtk::SelectionMode mode)
A change in the selection mode for the flow box.
void setDrawing(std::shared_ptr< PreviewDrawing > drawing)
void on_parent_changed()
Update the connection to the parent FlowBox.
void refresh(bool hide, guint32 bg_color)
BatchItem(SPItem *item, bool isolate_item, std::shared_ptr< PreviewDrawing > drawing)
void set_selected(bool selected)
Syncronise the FlowBox selection to the active widget activity.
void update_selected()
Syncronise the FlowBox selection to the existing active widget state.
void init(std::shared_ptr< PreviewDrawing > drawing)
sigc::scoped_connection _selection_widget_changed_conn
std::string get_suffix(int row)
Inkscape::Extension::Output * getExtension(int row)
void setSize(int newSize)
void setBox(Geom::Rect const &bbox)
void setDrawing(std::shared_ptr< PreviewDrawing > drawing)
void setBackgroundColor(std::uint32_t bg_color)
void setItem(SPItem const *item, bool is_layer=false)
static bool exportRaster(Geom::Rect const &area, unsigned long int const &width, unsigned long int const &height, float const &dpi, guint32 bg_color, Glib::ustring const &filename, bool overwrite, unsigned(*callback)(float, void *), void *data, Inkscape::Extension::Output *extension, std::vector< SPItem const * > *items=nullptr)
Export to raster graphics.
static bool exportVector(Inkscape::Extension::Output *extension, SPDocument *doc, Glib::ustring const &filename, bool overwrite, Geom::Rect const &area)
static bool unConflictFilename(SPDocument *doc, std::string &filename, std::string const extension)
Helperclass for Gtk::Entry widgets.
To do: update description of desktop.
SPDocument * getDocument() const
InkscapeWindow const * getInkscapeWindow() const
Inkscape::MessageStack * messageStack() const
SPNamedView * getNamedView() const
Inkscape::Selection * getSelection() const
Inkscape::LayerManager & layerManager()
Typed SVG document implementation.
SPRoot * getRoot()
Returns our SPRoot.
std::unique_ptr< SPDocument > copy() const
Create a copy of the document, useful for modifying during save & export.
char const * getDocumentFilename() const
Inkscape::PageManager & getPageManager()
SPNamedView * getNamedView()
Get the namedview for this document, creates it if it's not found.
Base class for visual SVG elements.
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
SPObject is an abstract base class of all of the document nodes at the SVG document level.
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
char const * getId() const
Returns the objects current ID string.
char const * defaultLabel() const
Returns a default label property for this object.
char const * getAttribute(char const *name) const
bool isAncestorOf(SPObject const *object) const
True if object is non-NULL and this is some in/direct parent of object.
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
std::string getDefaultLabel() const
Geom::Rect getDocumentRect() const
Get the rectangle of the page, scaled to the document.
Color picker button and window.
Editable view implementation.
static char const *const parent
TODO: insert short description here.
Macro for icon names used in Inkscape.
Inkscape - An SVG editor.
Raw stack of active status messages.
bool filesystem_is_sandboxed()
Query if the filesystem is "sandboxed", e.g., by using xdg-portal in flatpak/snap.
Glib::ustring filesystem_get_display_path(std::optional< Glib::RefPtr< Gio::File const > > path, Glib::ustring placeholder_if_empty)
Translate raw filesystem path to a path suitable for display.
void remove_file_extension(std::string &path)
Removes a file extension, if found, from the given path.
Inkscape::Colors::Color get_export_bg_color(SPObject *object, Inkscape::Colors::Color const &default_color)
static void set_sensitive(Gtk::SearchEntry2 &entry, bool const sensitive)
void set_export_bg_color(SPObject *object, Inkscape::Colors::Color const &color)
std::map< std::string, std::unique_ptr< BatchItem > > BatchItems
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
std::string optimizePath(std::string const &path, std::string const &base, unsigned int parents)
Convert an absolute path into a relative one if possible to do in the given number of parent steps.
static cairo_user_data_key_t key
Singleton class to access the preferences file in a convenient way.
SPRoot: SVG <svg> implementation.
Glib::RefPtr< Gtk::Builder > builder