diff --git a/src/FileFormat/AkiraFile.vala b/src/FileFormat/AkiraFile.vala
index 6a4e19101..8b71ec7e2 100644
--- a/src/FileFormat/AkiraFile.vala
+++ b/src/FileFormat/AkiraFile.vala
@@ -26,6 +26,8 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
public File pictures_folder { get; private set; }
public File thumbnails_folder { get; private set; }
+ public bool overwrite = false;
+
private File content_file { get; set; }
public string path {
owned get {
@@ -45,6 +47,7 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
new FileFormat.JsonLoader (window, content_json);
update_recent_list.begin ();
+
debug ("Version from file: %s", content_json.get_string_member ("version"));
} catch (Error e) {
error ("Could not load file: %s", e.message);
@@ -53,6 +56,7 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
public void save_file () {
try {
+ save_images.begin ();
var content = new FileFormat.JsonContent (window);
content.save_content ();
@@ -126,4 +130,50 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
window.app.update_recent_files_list ();
}
+
+ /**
+ * Save all the images used in the Canvas and make a copy in the Pictures folder.
+ */
+ public async void save_images () {
+ // Clear potential leftover images if we're overwriting an existing file.
+ if (overwrite) {
+ try {
+ Dir dir = Dir.open (pictures_folder.get_path (), 0);
+ string? name = null;
+ while ((name = dir.read_name ()) != null) {
+ var file = File.new_for_path (Path.build_filename (pictures_folder.get_path (), name));
+ file_collector.mark_for_deletion (file);
+ }
+ } catch (FileError err) {
+ stderr.printf (err.message);
+ }
+ overwrite = false;
+ }
+
+ foreach (var image in window.items_manager.images) {
+ var image_file = File.new_for_path (
+ Path.build_filename (pictures_folder.get_path (), image.manager.filename)
+ );
+
+ // Copy the file if it doesn't exist, or increase the reference count.
+ if (!image_file.query_exists ()) {
+ copy_image (image.manager.file, image_file);
+ continue;
+ }
+ }
+ }
+
+ /**
+ * Decrease the reference count to an existing image, which will cause its
+ * deletion if the count reaches 0.
+ */
+ public async void remove_image (string filename) {
+ var image_file = File.new_for_path (
+ Path.build_filename (pictures_folder.get_path (), filename)
+ );
+
+ if (image_file.query_exists ()) {
+ file_collector.unref_file (image_file);
+ }
+ }
}
diff --git a/src/FileFormat/FileManager.vala b/src/FileFormat/FileManager.vala
index 8b05fbf74..5754d1902 100644
--- a/src/FileFormat/FileManager.vala
+++ b/src/FileFormat/FileManager.vala
@@ -78,6 +78,8 @@ public class Akira.FileFormat.FileManager : Object {
}
private void save_file_as_response (Gtk.FileChooserNative dialog, int response_id) {
+ bool overwrite = false;
+
switch (response_id) {
case Gtk.ResponseType.ACCEPT:
File file;
@@ -85,10 +87,11 @@ public class Akira.FileFormat.FileManager : Object {
var path = save_file.get_path ();
if (path.has_suffix (".akira")) {
file = save_file;
+ overwrite = true;
} else {
file = File.new_for_path (path + ".akira");
}
- window.save_new_file (file);
+ window.save_new_file (file, overwrite);
window.event_bus.file_saved (dialog.get_current_name ());
break;
}
diff --git a/src/FileFormat/JsonObject.vala b/src/FileFormat/JsonObject.vala
index 8677896b9..1d40410c1 100644
--- a/src/FileFormat/JsonObject.vala
+++ b/src/FileFormat/JsonObject.vala
@@ -45,6 +45,7 @@ public class Akira.FileFormat.JsonObject : GLib.Object {
}
transform = new Json.Object ();
+
write_transform ();
}
@@ -91,8 +92,12 @@ public class Akira.FileFormat.JsonObject : GLib.Object {
} else if (type == typeof (Goo.CanvasItemVisibility)) {
item.get_property (spec.get_name (), ref val);
obj.set_int_member (spec.get_name (), val.get_enum ());
+ } else if (type == typeof (Akira.Lib.Managers.ImageManager)) {
+ var canvas_image = item as Akira.Lib.Models.CanvasImage;
+ obj.set_string_member ("image_id", canvas_image.manager.filename);
} else {
- // warning ("Property type %s not yet supported: %s\n", type.name (), spec.get_name ());
+ // Leave this comment for debug purpose.
+ // warning ("Property type %s not yet supported: %s\n", type.name (), spec.get_name ());
}
}
diff --git a/src/FileFormat/ZipArchiveHandler.vala b/src/FileFormat/ZipArchiveHandler.vala
index aa22c134a..945acebb7 100644
--- a/src/FileFormat/ZipArchiveHandler.vala
+++ b/src/FileFormat/ZipArchiveHandler.vala
@@ -367,6 +367,19 @@ public class Akira.FileFormat.ZipArchiveHandler : GLib.Object {
}
}
+ public virtual void copy_image (GLib.File old_file, GLib.File new_file) {
+ try {
+ old_file.copy (new_file, 0, null, (current_num_bytes, total_num_bytes) => {
+ // Report copy-status:
+ print ("%" + int64.FORMAT + " bytes of %" + int64.FORMAT + " bytes copied.\n",
+ current_num_bytes, total_num_bytes);
+ });
+ file_collector.ref_file (new_file);
+ } catch (Error e) {
+ print ("Error: %s\n", e.message);
+ }
+ }
+
/**
* Takes care of the reference counting for files inside the archive.
diff --git a/src/Layouts/Partials/TransformPanel.vala b/src/Layouts/Partials/TransformPanel.vala
index 3dbe20877..4b2e7f7e2 100644
--- a/src/Layouts/Partials/TransformPanel.vala
+++ b/src/Layouts/Partials/TransformPanel.vala
@@ -341,6 +341,8 @@ public class Akira.Layouts.Partials.TransformPanel : Gtk.Grid {
y.value = position["y"];
y.notify["value"].connect (y_notify_value);
}
+
+ window.event_bus.file_edited ();
}
public void x_notify_value () {
diff --git a/src/Lib/Managers/ImageManager.vala b/src/Lib/Managers/ImageManager.vala
new file mode 100644
index 000000000..6b5d418af
--- /dev/null
+++ b/src/Lib/Managers/ImageManager.vala
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 Alecaddd (https://alecaddd.com)
+ *
+ * This file is part of Akira.
+ *
+ * Akira is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * Akira is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with Akira. If not, see .
+ *
+ * Authored by: Adam Bieńkowski
+ * Authored by: Alessandro "Alecaddd" Castellani
+ */
+
+public class Akira.Lib.Managers.ImageManager : Object {
+ public GLib.File file { get; set; }
+
+ // Save the generated Pixbuf for later reference.
+ public Gdk.Pixbuf pixbuf;
+ // The unique name of the loaded image, including the current timestamp.
+ public string filename;
+
+ private const string[] ACCEPTED_TYPES = {
+ "image/jpeg",
+ "image/png",
+ "image/tiff",
+ "image/svg+xml",
+ "image/gif"
+ };
+
+ public ImageManager (GLib.File _file, int id) {
+ file = _file;
+
+ var timestamp = new GLib.DateTime.now_utc ();
+ filename = ("akira-img-%i-%s.%s").printf (
+ id,
+ timestamp.to_unix ().to_string (),
+ Utils.Image.get_extension (file)
+ );
+ }
+
+ /**
+ * Initialize a new ImageManager from a previously saved file.
+ * We use this to avoid changing the filename which should be unique.
+ *
+ * @param {GLib.File} _file - The file loaded from the saved archive.
+ * @param {string} _filename - The original filename of the saved file.
+ */
+ public ImageManager.from_archive (GLib.File _file, string _filename) {
+ file = _file;
+ filename = _filename;
+ }
+
+ /**
+ * Generate the Pixbuf from the given file. This method is also called to
+ * resample the quality of the pixbuf when the image is resized.
+ *
+ * @param {int} width - The requested width for the resample.
+ * @param {int} height - The requested height for the resample.
+ * @return Gdk.Pixbuf
+ */
+ public async Gdk.Pixbuf get_pixbuf (int width = -1, int height = -1) throws Error {
+ FileInputStream stream;
+
+ try {
+ stream = yield file.read_async ();
+ } catch (Error e) {
+ throw e;
+ }
+
+ if (width != -1 && height != -1) {
+ try {
+ pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (stream, width, height, false);
+ return pixbuf;
+ } catch (Error e) {
+ throw e;
+ }
+ } else {
+ try {
+ pixbuf = yield new Gdk.Pixbuf.from_stream_async (stream);
+ return pixbuf;
+ } catch (Error e) {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/src/Lib/Managers/ItemsManager.vala b/src/Lib/Managers/ItemsManager.vala
index 73d32dd4b..d700227a0 100644
--- a/src/Lib/Managers/ItemsManager.vala
+++ b/src/Lib/Managers/ItemsManager.vala
@@ -25,6 +25,7 @@ public class Akira.Lib.Managers.ItemsManager : Object {
public Akira.Models.ListModel free_items;
public Akira.Models.ListModel artboards;
+ public Akira.Models.ListModel images;
private Models.CanvasItemType? insert_type { get; set; }
private Goo.CanvasItem root;
private int border_size;
@@ -40,6 +41,7 @@ public class Akira.Lib.Managers.ItemsManager : Object {
construct {
free_items = new Akira.Models.ListModel ();
artboards = new Akira.Models.ListModel ();
+ images = new Akira.Models.ListModel ();
border_color = Gdk.RGBA ();
fill_color = Gdk.RGBA ();
@@ -50,7 +52,7 @@ public class Akira.Lib.Managers.ItemsManager : Object {
window.event_bus.hold_released.connect (on_hold_released);
}
- public void insert_image (Services.FileImageProvider provider) {
+ public void insert_image (Lib.Managers.ImageManager manager) {
var selected_bound_manager = window.main_window.main_canvas.canvas.selected_bound_manager;
double start_x, start_y, scale, rotation;
@@ -74,7 +76,8 @@ public class Akira.Lib.Managers.ItemsManager : Object {
}
set_item_to_insert ("image");
- var new_item = insert_item (start_x, start_y, provider);
+ var new_item = insert_item (start_x, start_y, manager);
+ (new_item as Models.CanvasImage).resize_pixbuf (-1, -1, true);
selected_bound_manager.add_item_to_selection (new_item);
selected_bound_manager.set_initial_coordinates (start_x, start_y);
@@ -83,7 +86,8 @@ public class Akira.Lib.Managers.ItemsManager : Object {
public Models.CanvasItem? insert_item (
double x,
double y,
- Services.FileImageProvider? provider = null
+ Lib.Managers.ImageManager? manager = null,
+ bool loaded = false
) {
udpate_default_values ();
@@ -106,15 +110,15 @@ public class Akira.Lib.Managers.ItemsManager : Object {
switch (insert_type) {
case Models.CanvasItemType.RECT:
- new_item = add_rect (x, y, root, artboard);
+ new_item = add_rect (x, y, root, artboard, loaded);
break;
case Models.CanvasItemType.ELLIPSE:
- new_item = add_ellipse (x, y, root, artboard);
+ new_item = add_ellipse (x, y, root, artboard, loaded);
break;
case Models.CanvasItemType.TEXT:
- new_item = add_text (x, y, root, artboard);
+ new_item = add_text (x, y, root, artboard, loaded);
break;
case Models.CanvasItemType.ARTBOARD:
@@ -122,7 +126,7 @@ public class Akira.Lib.Managers.ItemsManager : Object {
break;
case Models.CanvasItemType.IMAGE:
- new_item = add_image (x, y, provider, root, artboard);
+ new_item = add_image (x, y, manager, root, artboard, loaded);
break;
}
@@ -132,10 +136,17 @@ public class Akira.Lib.Managers.ItemsManager : Object {
artboards.add_item.begin ((Models.CanvasArtboard) new_item);
break;
+ case Akira.Lib.Models.CanvasItemType.IMAGE:
default:
+ // We need to store images in a dedicated list since we will need
+ // to easily access them and save them in the .akira/Pictures folder.
+ if (new_item.item_type == Akira.Lib.Models.CanvasItemType.IMAGE) {
+ images.add_item.begin ((new_item as Akira.Lib.Models.CanvasImage), loaded);
+ }
+
if (new_item.artboard == null) {
// Add it to "free items"
- free_items.add_item.begin (new_item, false);
+ free_items.add_item.begin (new_item, loaded);
}
break;
}
@@ -158,7 +169,20 @@ public class Akira.Lib.Managers.ItemsManager : Object {
artboards.remove_item.begin (item as Models.CanvasArtboard);
break;
+ case Akira.Lib.Models.CanvasItemType.IMAGE:
default:
+ // Remove the image from the list so we don't keep it in the saved file.
+ if (item.item_type == Akira.Lib.Models.CanvasItemType.IMAGE) {
+ images.remove_item.begin ((item as Akira.Lib.Models.CanvasImage));
+
+ // Mark it for removal if we have a saved file.
+ if (window.akira_file != null) {
+ window.akira_file.remove_image.begin (
+ (item as Akira.Lib.Models.CanvasImage).manager.filename
+ );
+ }
+ }
+
if (item.artboard == null) {
free_items.remove_item.begin (item);
}
@@ -179,7 +203,13 @@ public class Akira.Lib.Managers.ItemsManager : Object {
return artboard as Models.CanvasItem;
}
- public Models.CanvasItem add_rect (double x, double y, Goo.CanvasItem parent, Models.CanvasArtboard? artboard) {
+ public Models.CanvasItem add_rect (
+ double x,
+ double y,
+ Goo.CanvasItem parent,
+ Models.CanvasArtboard? artboard,
+ bool loaded
+ ) {
return new Models.CanvasRect (
Utils.AffineTransform.fix_size (x),
Utils.AffineTransform.fix_size (y),
@@ -189,10 +219,18 @@ public class Akira.Lib.Managers.ItemsManager : Object {
border_color,
fill_color,
parent,
- artboard);
+ artboard,
+ loaded
+ );
}
- public Models.CanvasEllipse add_ellipse (double x, double y, Goo.CanvasItem parent, Models.CanvasArtboard? artboard) {
+ public Models.CanvasEllipse add_ellipse (
+ double x,
+ double y,
+ Goo.CanvasItem parent,
+ Models.CanvasArtboard? artboard,
+ bool loaded
+ ) {
return new Models.CanvasEllipse (
Utils.AffineTransform.fix_size (x),
Utils.AffineTransform.fix_size (y),
@@ -202,10 +240,18 @@ public class Akira.Lib.Managers.ItemsManager : Object {
border_color,
fill_color,
parent,
- artboard);
+ artboard,
+ loaded
+ );
}
- public Models.CanvasText add_text (double x, double y, Goo.CanvasItem parent, Models.CanvasArtboard? artboard) {
+ public Models.CanvasText add_text (
+ double x,
+ double y,
+ Goo.CanvasItem parent,
+ Models.CanvasArtboard? artboard,
+ bool loaded
+ ) {
return new Models.CanvasText (
"Akira is awesome :)",
Utils.AffineTransform.fix_size (x),
@@ -215,22 +261,27 @@ public class Akira.Lib.Managers.ItemsManager : Object {
Goo.CanvasAnchorType.NW,
"Open Sans 18",
parent,
- artboard);
+ artboard,
+ loaded
+ );
}
public Models.CanvasImage add_image (
double x,
double y,
- Services.FileImageProvider provider,
+ Lib.Managers.ImageManager manager,
Goo.CanvasItem parent,
- Models.CanvasArtboard? artboard
+ Models.CanvasArtboard? artboard,
+ bool loaded
) {
return new Models.CanvasImage (
Utils.AffineTransform.fix_size (x),
Utils.AffineTransform.fix_size (y),
- provider,
+ manager,
parent,
- artboard);
+ artboard,
+ loaded
+ );
}
public int get_item_position (Lib.Models.CanvasItem item) {
@@ -386,22 +437,35 @@ public class Akira.Lib.Managers.ItemsManager : Object {
switch (obj.get_string_member ("type")) {
case "AkiraLibModelsCanvasRect":
insert_type = Models.CanvasItemType.RECT;
- item = insert_item (pos_x, pos_y);
+ item = insert_item (pos_x, pos_y, null, true);
break;
case "AkiraLibModelsCanvasEllipse":
insert_type = Models.CanvasItemType.ELLIPSE;
- item = insert_item (pos_x, pos_y);
+ item = insert_item (pos_x, pos_y, null, true);
break;
case "AkiraLibModelsCanvasText":
insert_type = Models.CanvasItemType.TEXT;
- item = insert_item (pos_x, pos_y);
+ item = insert_item (pos_x, pos_y, null, true);
break;
case "AkiraLibModelsCanvasArtboard":
insert_type = Models.CanvasItemType.ARTBOARD;
- item = insert_item (pos_x, pos_y);
+ item = insert_item (pos_x, pos_y, null, true);
+ break;
+
+ case "AkiraLibModelsCanvasImage":
+ insert_type = Models.CanvasItemType.IMAGE;
+ var filename = obj.get_string_member ("image_id");
+ var file = File.new_for_path (
+ Path.build_filename (
+ window.akira_file.pictures_folder.get_path (),
+ filename
+ )
+ );
+ var manager = new Akira.Lib.Managers.ImageManager.from_archive (file, filename);
+ item = insert_item (pos_x, pos_y, manager, true);
break;
}
@@ -441,6 +505,15 @@ public class Akira.Lib.Managers.ItemsManager : Object {
item.set ("global-radius", obj.get_double_member ("global-radius"));
}
+ // Restore image size.
+ if (item is Models.CanvasImage) {
+ (item as Models.CanvasImage).resize_pixbuf (
+ (int) obj.get_double_member ("width"),
+ (int) obj.get_double_member ("height"),
+ true
+ );
+ }
+
// Restore layer options.
item.locked = obj.get_boolean_member ("locked");
item.visibility =
@@ -480,7 +553,7 @@ public class Akira.Lib.Managers.ItemsManager : Object {
}
}
- /*
+ /**
* Restore the selected status of an object.
*
* @param bool selected - If the object is selected.
@@ -504,6 +577,15 @@ public class Akira.Lib.Managers.ItemsManager : Object {
return;
}
+ // If we have images in the canvas, check if they're part of the selection to recalculate the size.
+ if (images.get_n_items () > 0) {
+ foreach (var image in images) {
+ if (items.find (image) != null) {
+ image.check_resize_pixbuf ();
+ }
+ }
+ }
+
// Interrupt if no artboard is currently present.
if (artboards.get_n_items () == 0) {
return;
diff --git a/src/Lib/Models/CanvasArtboard.vala b/src/Lib/Models/CanvasArtboard.vala
index 4f931aa9f..97abfabc2 100644
--- a/src/Lib/Models/CanvasArtboard.vala
+++ b/src/Lib/Models/CanvasArtboard.vala
@@ -95,6 +95,9 @@ public class Akira.Lib.Models.CanvasArtboard : Goo.CanvasItemSimple, Goo.CanvasI
public double initial_relative_x { get; set; }
public double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public bool loaded { get; set; default = false; }
+
public CanvasArtboard (double _x = 0, double _y = 0, Goo.CanvasItem? _parent = null) {
parent_item = _parent;
@@ -172,7 +175,7 @@ public class Akira.Lib.Models.CanvasArtboard : Goo.CanvasItemSimple, Goo.CanvasI
return;
}
- items.add_item.begin (canvas_item, false);
+ items.add_item.begin (canvas_item, (item as Models.CanvasItem).loaded);
item.set_parent (this);
request_update ();
diff --git a/src/Lib/Models/CanvasEllipse.vala b/src/Lib/Models/CanvasEllipse.vala
index 7add0b44c..359fdc2c3 100644
--- a/src/Lib/Models/CanvasEllipse.vala
+++ b/src/Lib/Models/CanvasEllipse.vala
@@ -69,6 +69,9 @@ public class Akira.Lib.Models.CanvasEllipse : Goo.CanvasEllipse, Models.CanvasIt
public double initial_relative_x { get; set; }
public double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public bool loaded { get; set; default = false; }
+
public CanvasEllipse (
double _center_x = 0,
double _center_y = 0,
@@ -78,8 +81,10 @@ public class Akira.Lib.Models.CanvasEllipse : Goo.CanvasEllipse, Models.CanvasIt
Gdk.RGBA _border_color,
Gdk.RGBA _fill_color,
Goo.CanvasItem? _parent = null,
- Models.CanvasArtboard? _artboard = null
+ Models.CanvasArtboard? _artboard = null,
+ bool _loaded = false
) {
+ loaded = _loaded;
artboard = _artboard;
parent = _artboard != null ? _artboard : _parent;
canvas = parent.get_canvas () as Akira.Lib.Canvas;
diff --git a/src/Lib/Models/CanvasImage.vala b/src/Lib/Models/CanvasImage.vala
index f67dac51a..567fd276d 100644
--- a/src/Lib/Models/CanvasImage.vala
+++ b/src/Lib/Models/CanvasImage.vala
@@ -76,17 +76,29 @@ public class Akira.Lib.Models.CanvasImage : Goo.CanvasImage, Models.CanvasItem {
public double initial_relative_x { get; set; }
public double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public bool loaded { get; set; default = false; }
+
+ // CanvasImage unique attributes.
+ public Lib.Managers.ImageManager manager { get; set; }
+ private Gdk.Pixbuf original_pixbuf;
+
public CanvasImage (
double _x = 0,
double _y = 0,
- Services.ImageProvider provider,
+ Lib.Managers.ImageManager _manager,
Goo.CanvasItem? _parent = null,
- Models.CanvasArtboard? _artboard = null
+ Models.CanvasArtboard? _artboard = null,
+ bool _loaded = false
) {
+ loaded = _loaded;
artboard = _artboard;
parent = _artboard != null ? _artboard : _parent;
canvas = parent.get_canvas () as Akira.Lib.Canvas;
+ // Set the ImageManager.
+ manager = _manager;
+
item_type = Models.CanvasItemType.IMAGE;
id = Models.CanvasItem.create_item_id (this);
Models.CanvasItem.init_item (this);
@@ -106,25 +118,53 @@ public class Akira.Lib.Models.CanvasImage : Goo.CanvasImage, Models.CanvasItem {
position_item (_x, _y);
- provider.get_pixbuf.begin (-1, -1, (obj, res) => {
+ // Save the unedited pixbuf to enable resampling and restoring.
+ manager.get_pixbuf.begin (-1, -1, (obj, res) => {
try {
- var _pixbuf = provider.get_pixbuf.end (res);
- pixbuf = _pixbuf;
- width = _pixbuf.get_width ();
- height = _pixbuf.get_height ();
- fix_image_size ();
+ original_pixbuf = manager.get_pixbuf.end (res);
+ // Imported images should keep their aspect ratio by default.
+ size_ratio = original_pixbuf.get_width () / original_pixbuf.get_height ();
+ size_locked = true;
} catch (Error e) {
warning (e.message);
- // TODO: handle error here
+ canvas.window.event_bus.canvas_notification (e.message);
}
});
reset_colors ();
}
- public void fix_image_size () {
- // Imported images should keep their aspect ratio by default.
- size_ratio = width / height;
- size_locked = true;
+ /**
+ * Trigger the pixbuf resampling only if the image size changed.
+ */
+ public void check_resize_pixbuf () {
+ if (width == manager.pixbuf.get_width () && height == manager.pixbuf.get_height ()) {
+ return;
+ }
+
+ resize_pixbuf ((int) width, (int) height);
+ }
+
+ /**
+ * Resample the pixbuf size.
+ *
+ * @param {int} w - The new width.
+ * @param {int} h - The new height.
+ * @param {bool} update - If the updated pixbuf size should be applied to the CanvasItem.
+ */
+ public void resize_pixbuf (int w, int h, bool update = false) {
+ manager.get_pixbuf.begin (w, h, (obj, res) => {
+ try {
+ var _pixbuf = manager.get_pixbuf.end (res);
+ pixbuf = _pixbuf;
+ if (update) {
+ width = _pixbuf.get_width ();
+ height = _pixbuf.get_height ();
+ }
+ } catch (Error e) {
+ warning (e.message);
+ canvas.window.event_bus.canvas_notification (e.message);
+ }
+ });
}
}
diff --git a/src/Lib/Models/CanvasItem.vala b/src/Lib/Models/CanvasItem.vala
index a77ed0102..7f02654e8 100644
--- a/src/Lib/Models/CanvasItem.vala
+++ b/src/Lib/Models/CanvasItem.vala
@@ -80,6 +80,9 @@ public interface Akira.Lib.Models.CanvasItem : Goo.CanvasItemSimple, Goo.CanvasI
public abstract double initial_relative_x { get; set; }
public abstract double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public abstract bool loaded { get; set; default = false; }
+
public double get_coords (string coord_id) {
double _coord = 0.0;
diff --git a/src/Lib/Models/CanvasRect.vala b/src/Lib/Models/CanvasRect.vala
index 68bcf6c12..f653840d8 100644
--- a/src/Lib/Models/CanvasRect.vala
+++ b/src/Lib/Models/CanvasRect.vala
@@ -83,6 +83,9 @@ public class Akira.Lib.Models.CanvasRect : Goo.CanvasRect, Models.CanvasItem {
public double initial_relative_x { get; set; }
public double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public bool loaded { get; set; default = false; }
+
public CanvasRect (
double _x = 0,
double _y = 0,
@@ -92,8 +95,10 @@ public class Akira.Lib.Models.CanvasRect : Goo.CanvasRect, Models.CanvasItem {
Gdk.RGBA _border_color,
Gdk.RGBA _fill_color,
Goo.CanvasItem? _parent = null,
- Models.CanvasArtboard? _artboard = null
+ Models.CanvasArtboard? _artboard = null,
+ bool _loaded = false
) {
+ loaded = _loaded;
artboard = _artboard;
parent = _artboard != null ? _artboard : _parent;
canvas = parent.get_canvas () as Akira.Lib.Canvas;
diff --git a/src/Lib/Models/CanvasText.vala b/src/Lib/Models/CanvasText.vala
index 23636e1e5..789ff5ed8 100644
--- a/src/Lib/Models/CanvasText.vala
+++ b/src/Lib/Models/CanvasText.vala
@@ -69,6 +69,9 @@ public class Akira.Lib.Models.CanvasText : Goo.CanvasText, Models.CanvasItem {
public double initial_relative_x { get; set; }
public double initial_relative_y { get; set; }
+ // Knows if an item was created or loaded for ordering purpose.
+ public bool loaded { get; set; default = false; }
+
public CanvasText (
string _text = "",
double _x = 0,
@@ -78,7 +81,8 @@ public class Akira.Lib.Models.CanvasText : Goo.CanvasText, Models.CanvasItem {
Goo.CanvasAnchorType _anchor = Goo.CanvasAnchorType.NW,
string _font = "Open Sans 16",
Goo.CanvasItem? _parent = null,
- Models.CanvasArtboard? _artboard = null
+ Models.CanvasArtboard? _artboard = null,
+ bool _loaded = false
) {
Object (
x: _x,
@@ -87,6 +91,7 @@ public class Akira.Lib.Models.CanvasText : Goo.CanvasText, Models.CanvasItem {
height: _height
);
+ loaded = _loaded;
artboard = _artboard;
parent = _artboard != null ? _artboard : _parent;
canvas = parent.get_canvas () as Akira.Lib.Canvas;
diff --git a/src/Services/ActionManager.vala b/src/Services/ActionManager.vala
index 40a08d6a2..9780b80a2 100644
--- a/src/Services/ActionManager.vala
+++ b/src/Services/ActionManager.vala
@@ -360,8 +360,16 @@ public class Akira.Services.ActionManager : Object {
case Gtk.ResponseType.OK:
SList files = dialog.get_files ();
files.@foreach ((file) => {
- var provider = new Akira.Services.FileImageProvider (file);
- window.items_manager.insert_image (provider);
+ if (Akira.Utils.Image.is_valid_image (file)) {
+ var manager = new Akira.Lib.Managers.ImageManager (file, files.index (file));
+ window.items_manager.insert_image (manager);
+ return;
+ }
+
+ var ext = Akira.Utils.Image.get_extension (file);
+ window.event_bus.canvas_notification (
+ _("Error! .%s files are not supported!"
+ ).printf (ext));
});
break;
}
diff --git a/src/Services/ImageProvider.vala b/src/Services/ImageProvider.vala
deleted file mode 100644
index 062d3c3cc..000000000
--- a/src/Services/ImageProvider.vala
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-* Copyright (c) 2020 Adam Bieńkowski
-*
-* This file is part of Akira.
-*
-* Akira is free software: you can redistribute it and/or modify
-* it under the terms of the GNU General Public License as published by
-* the Free Software Foundation, either version 3 of the License, or
-* (at your option) any later version.
-
-* Akira is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-
-* You should have received a copy of the GNU General Public License
-* along with Akira. If not, see .
-*
-* Authored by: Adam Bieńkowski
-*/
-
-public interface Akira.Services.ImageProvider : Object {
- public abstract async Gdk.Pixbuf get_pixbuf (int width = -1, int height = -1) throws Error;
-}
-
-public class Akira.Services.FileImageProvider : ImageProvider, Object {
- public File file { get; construct; }
-
- public FileImageProvider (File file) {
- Object (file: file);
- }
-
- public async Gdk.Pixbuf get_pixbuf (int width = -1, int height = -1) throws Error {
- FileInputStream stream;
- try {
- stream = yield file.read_async ();
- } catch (Error e) {
- throw e;
- }
-
- if (width != -1 && height != -1) {
- try {
- return yield new Gdk.Pixbuf.from_stream_at_scale_async (stream, width, height, false);
- } catch (Error e) {
- throw e;
- }
- } else {
- try {
- return yield new Gdk.Pixbuf.from_stream_async (stream);
- } catch (Error e) {
- throw e;
- }
- }
- }
-}
diff --git a/src/Utils/Image.vala b/src/Utils/Image.vala
new file mode 100644
index 000000000..03c7371b5
--- /dev/null
+++ b/src/Utils/Image.vala
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 Alecaddd (https://alecaddd.com)
+ *
+ * This file is part of Akira.
+ *
+ * Akira is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * Akira is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with Akira. If not, see .
+ *
+ * Authored by: Alessandro "Alecaddd" Castellani
+ */
+
+public class Akira.Utils.Image : Object {
+ private const string[] ACCEPTED_TYPES = {
+ "image/jpeg",
+ "image/png",
+ "image/tiff",
+ "image/svg+xml",
+ "image/gif"
+ };
+
+ /**
+ * Check if the filename has a picture file extension.
+ */
+ public static bool is_valid_image (GLib.File file) {
+ try {
+ var file_info = file.query_info ("standard::*", 0);
+
+ // Check for correct file type, don't try to load directories.
+ if (file_info.get_file_type () != GLib.FileType.REGULAR) {
+ return false;
+ }
+ try {
+ var pixbuf = new Gdk.Pixbuf.from_file (file.get_path ());
+ var width = pixbuf.get_width ();
+ var height = pixbuf.get_height ();
+
+ if (width < 1 || height < 1) return false;
+ } catch (Error e) {
+ warning ("Invalid image loaded: %s", e.message);
+ return false;
+ }
+
+ foreach (var type in ACCEPTED_TYPES) {
+ if (GLib.ContentType.equals (file_info.get_content_type (), type)) {
+ return true;
+ }
+ }
+ } catch (Error e) {
+ warning ("Could not get file info: %s", e.message);
+ }
+
+ return false;
+ }
+
+ /**
+ * Return only the file extension, PNG if not extension was found.
+ */
+ public static string get_extension (GLib.File file) {
+ var parts = file.get_basename ().split (".");
+
+ return parts.length > 1 ? parts[parts.length - 1] : "png";
+ }
+}
diff --git a/src/Window.vala b/src/Window.vala
index e31152505..c42f91b5b 100644
--- a/src/Window.vala
+++ b/src/Window.vala
@@ -185,8 +185,9 @@ public class Akira.Window : Gtk.ApplicationWindow {
akira_file.load_file ();
}
- public void save_new_file (File file) {
+ public void save_new_file (File file, bool overwrite = false) {
akira_file = new FileFormat.AkiraFile (file, this);
+ akira_file.overwrite = overwrite;
akira_file.prepare ();
akira_file.save_file ();
diff --git a/src/meson.build b/src/meson.build
index a4241d0aa..bcacf3f98 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,12 +29,12 @@ sources = files(
'Services/Settings.vala',
'Services/ActionManager.vala',
'Services/EventBus.vala',
- 'Services/ImageProvider.vala',
'Utils/Dialogs.vala',
'Utils/BlendingMode.vala',
'Utils/AffineTransform.vala',
'Utils/Color.vala',
+ 'Utils/Image.vala',
'Layouts/HeaderBar.vala',
'Layouts/LeftSideBar.vala',
@@ -82,6 +82,7 @@ sources = files(
'Lib/Managers/HoverManager.vala',
'Lib/Managers/SelectedBoundManager.vala',
'Lib/Managers/ItemsManager.vala',
+ 'Lib/Managers/ImageManager.vala',
'Lib/Managers/NobManager.vala',
'Lib/Models/CanvasItem.vala',