Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow permanantly saving images (Requires #146) #147

Merged
merged 2 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions [email protected]/adapter/baseAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ var BaseAdapter = class {
*/
fetchFile(uri, callback) {
//extract the name from the url and
let date = new Date();
let name = date.getTime() + '_' + this.fileName(uri); // timestamp ensures uniqueness
let name = this.fileName(uri);

let bowl = new SoupBowl.Bowl();

Expand Down
3 changes: 1 addition & 2 deletions [email protected]/adapter/localFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ var LocalFolderAdapter = class extends BaseAdapter.BaseAdapter {
}

fetchFile(path, callback) {
let date = new Date();
let sourceFile = Gio.file_new_for_path(path);
let name = `${date.getTime()}_${sourceFile.get_basename()}`
let name = sourceFile.get_basename()
let targetFile = Gio.file_new_for_path(this._wallpaperLocation + String(name));

// https://gjs.guide/guides/gio/file-operations.html#copying-and-moving-files
Expand Down
54 changes: 43 additions & 11 deletions [email protected]/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ const Util = imports.misc.util;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Clutter = imports.gi.Clutter;
const Cogl = imports.gi.Cogl;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const GObject = imports.gi.GObject;

const Self = imports.misc.extensionUtils.getCurrentExtension();
const Settings = Self.imports.settings;
const LoggerModule = Self.imports.logger;
const Timer = Self.imports.timer;

var HistoryElement = GObject.registerClass({
GTypeName: 'HistoryElement',
}, class HistoryElement extends PopupMenu.PopupSubMenuMenuItem {
_init(historyEntry, index) {
GTypeName: 'HistoryElement',
}, class HistoryElement extends PopupMenu.PopupSubMenuMenuItem {
_init(historyEntry, index) {
super._init("", false);
this.logger = new LoggerModule.Logger('RWG3', 'HistoryElement');
this.historyEntry = null;
this.setAsWallpaperItem = null;
this.previewItem = null;
this._previewActor = null;
this._settings = new Settings.Settings();

let timestamp = historyEntry.timestamp;
let date = new Date(timestamp);
Expand Down Expand Up @@ -97,17 +100,46 @@ var HistoryElement = GObject.registerClass({
this.menu.addMenuItem(new PopupMenu.PopupMenuItem('Unknown source.'));
}

this.copyToFavorites = new PopupMenu.PopupMenuItem('Save for later');
this.copyToFavorites.connect('activate', () => {
let sourceFile = Gio.File.new_for_path(this.historyEntry.path);
let targetFolder = Gio.File.new_for_path(this._settings.get('favorites-folder', 'string'));
let targetFile = targetFolder.get_child(historyEntry.name);

try {
if (!targetFolder.make_directory_with_parents(null)) {
this.logger.warning('Could not create directories.');
return;
}
} catch (error) {
if (error === Gio.IOErrorEnum.EXISTS) { }
ifl0w marked this conversation as resolved.
Show resolved Hide resolved
}

try {
if (!sourceFile.copy(targetFile, Gio.FileCopyFlags.NONE, null, null)) {
this.logger.warning('Failed copying image.');
ifl0w marked this conversation as resolved.
Show resolved Hide resolved
return;
}
} catch (error) {
if (error === Gio.IOErrorEnum.EXISTS) {
this.logger.warning('Image already exists in location.');
ifl0w marked this conversation as resolved.
Show resolved Hide resolved
return;
}
}
});
this.menu.addMenuItem(this.copyToFavorites);

this.setAsWallpaperItem = new PopupMenu.PopupMenuItem('Set As Wallpaper');
this.setAsWallpaperItem.connect('activate', () => {
this.emit('activate', null); // Fixme: not sure what the second parameter should be. null seems to work fine for now.
});

if (index !== 0) {
this.menu.addMenuItem(new PopupMenu.PopupBaseMenuItem({can_focus: false, reactive: false})); // theme independent spacing
this.menu.addMenuItem(new PopupMenu.PopupBaseMenuItem({ can_focus: false, reactive: false })); // theme independent spacing
this.menu.addMenuItem(this.setAsWallpaperItem);
}

this.previewItem = new PopupMenu.PopupBaseMenuItem({can_focus: false, reactive: false});
this.previewItem = new PopupMenu.PopupBaseMenuItem({ can_focus: false, reactive: false });
this.menu.addMenuItem(this.previewItem);

/*
Expand All @@ -133,7 +165,7 @@ var HistoryElement = GObject.registerClass({
height,
pixbuf.get_rowstride()
);
this._previewActor = new Clutter.Actor({height: height, width: width});
this._previewActor = new Clutter.Actor({ height: height, width: width });
this._previewActor.set_content(image);

this.previewItem.actor.add_actor(this._previewActor);
Expand All @@ -147,12 +179,12 @@ var HistoryElement = GObject.registerClass({
setIndex(index) {
this.prefixLabel.set_text(String(index));
}
}
}
);

var CurrentImageElement = GObject.registerClass({
GTypeName: 'CurrentImageElement',
}, class CurrentImageElement extends HistoryElement {
GTypeName: 'CurrentImageElement',
}, class CurrentImageElement extends HistoryElement {

_init(historyElement) {
super._init(historyElement, 0);
Expand All @@ -170,8 +202,8 @@ var CurrentImageElement = GObject.registerClass({
* The remaining time will only be displayed if the af-feature is activated.
*/
var NewWallpaperElement = GObject.registerClass({
GTypeName: 'NewWallpaperElement',
}, class NewWallpaperElement extends PopupMenu.PopupBaseMenuItem {
GTypeName: 'NewWallpaperElement',
}, class NewWallpaperElement extends PopupMenu.PopupBaseMenuItem {

_init(params) {
super._init(params);
Expand Down
3 changes: 2 additions & 1 deletion [email protected]/history.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var HistoryEntry = class {

constructor(author, source, url) {
this.id = null;
this.name = null;
this.path = null;
this.source = null;
this.timestamp = new Date().getTime();
Expand Down Expand Up @@ -152,4 +153,4 @@ var HistoryController = class {
}
}

};
};
53 changes: 44 additions & 9 deletions [email protected]/prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ var RandomWallpaperSettings = class {

this._loadSources();

this._settings.bind('history-length',
this._builder.get_object('history_length'),
'value',
Gio.SettingsBindFlags.DEFAULT);
this._settings.bind('minutes',
this._builder.get_object('duration_minutes'),
'value',
Expand Down Expand Up @@ -91,6 +87,7 @@ var RandomWallpaperSettings = class {
Gio.SettingsBindFlags.DEFAULT);

this._bindButtons();
this._bindHistorySection(window);

window.connect('close-request', () => {
this._saveSources();
Expand Down Expand Up @@ -128,6 +125,28 @@ var RandomWallpaperSettings = class {
this._backendConnection.set('request-new-wallpaper', 'boolean', true);
});

this._builder.get_object('button_new_source').connect('clicked', () => {
let source_row = new SourceRow.SourceRow();
this.available_rows[source_row.id] = source_row;
this._builder.get_object('sources_list').add(source_row);

this._bindSourceRow(source_row);
});
}

_bindHistorySection(window) {
let entryRow = this._builder.get_object('row_favorites_folder');
entryRow.text = this._settings.get_string('favorites-folder');

this._settings.bind('history-length',
this._builder.get_object('history_length'),
'value',
Gio.SettingsBindFlags.DEFAULT);
this._settings.bind('favorites-folder',
entryRow,
'text',
Gio.SettingsBindFlags.DEFAULT);

this._builder.get_object('clear_history').connect('clicked', () => {
this._backendConnection.set('clear-history', 'boolean', true);
});
Expand All @@ -136,12 +155,28 @@ var RandomWallpaperSettings = class {
this._backendConnection.set('open-folder', 'boolean', true);
});

this._builder.get_object('button_new_source').connect('clicked', () => {
let source_row = new SourceRow.SourceRow();
this.available_rows[source_row.id] = source_row;
this._builder.get_object('sources_list').add(source_row);
this._builder.get_object('button_favorites_folder').connect('clicked', () => {
// For GTK 4.10+
// Gtk.FileDialog();

// https://stackoverflow.com/a/54487948
this._saveDialog = new Gtk.FileChooserNative({
title: 'Choose a Wallpaper Folder',
ifl0w marked this conversation as resolved.
Show resolved Hide resolved
action: Gtk.FileChooserAction.SELECT_FOLDER,
accept_label: 'Open',
cancel_label: 'Cancel',
transient_for: window,
modal: true,
});

this._bindSourceRow(source_row);
this._saveDialog.connect('response', (dialog, response_id) => {
if (response_id === Gtk.ResponseType.ACCEPT) {
entryRow.text = this._saveDialog.get_file().get_path();
}
this._saveDialog.destroy();
});

this._saveDialog.show();
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
<description>A mapping with available configured wallpaper sources as stringified JSON.</description>
</key>

<key type='s' name='favorites-folder'>
<default>""</default>
<summary>Favorites Folder</summary>
<description>Saved images will land in this folder.</description>
</key>

</schema>

<schema path="/org/gnome/shell/extensions/space-iflow-randomwallpaper/backend-connection/"
Expand Down
2 changes: 1 addition & 1 deletion [email protected]/ui/localFolder.blp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ template LocalFolderSettingsGroup : Adw.PreferencesGroup {
valign: center;

Adw.ButtonContent {
icon-name: "document-open-symbolic";
icon-name: "folder-open-symbolic";
}
}
}
Expand Down
48 changes: 30 additions & 18 deletions [email protected]/ui/pageGeneral.blp
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,6 @@ Adw.PreferencesPage page_general {
Adw.PreferencesGroup {
title: _("History");

Adw.ActionRow {
title: _("History length");
subtitle: _("The number of wallpapers that will be shown in the history and stored in the wallpaper folder of this extension.");

SpinButton {
valign: center;
numeric: true;

adjustment: Adjustment history_length {
lower: 1;
upper: 100;
value: 10;
step-increment: 1;
page-increment: 10;
};
}
}

header-suffix: Box {
spacing: 14;

Expand All @@ -100,6 +82,36 @@ Adw.PreferencesPage page_general {
]
}
};

Adw.ActionRow {
title: _("History length");
subtitle: _("The number of wallpapers that will be shown in the history and stored in the wallpaper folder of this extension.");

SpinButton {
valign: center;
numeric: true;

adjustment: Adjustment history_length {
lower: 1;
upper: 100;
value: 10;
step-increment: 1;
page-increment: 10;
};
}
}

Adw.EntryRow row_favorites_folder{
title: _("Save for later folder");

Button button_favorites_folder {
valign: center;

Adw.ButtonContent {
icon-name: "folder-open-symbolic";
}
}
}
}

Adw.PreferencesGroup {
Expand Down
50 changes: 47 additions & 3 deletions [email protected]/wallpaperController.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ var WallpaperController = class {
}

this.currentWallpaper = this._getCurrentWallpaper();

// Initialize favorites folder
// TODO: There's probably a better place for this
let favoritesFolder = this._settings.get('favorites-folder', 'string');
if (favoritesFolder === "") {
const directoryPictures = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);

if (directoryPictures === null) {
// Pictures not set up
const directoryDownloads = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOWNLOAD);

if (directoryDownloads === null) {
const xdg_data_home = GLib.get_user_data_dir();
favoritesFolder = Gio.File.new_for_path(xdg_data_home);
} else {
favoritesFolder = Gio.File.new_for_path(directoryDownloads);
}
} else {
favoritesFolder = Gio.File.new_for_path(directoryPictures);
}
Comment on lines +84 to +99
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this fallback chain is the best idea. It could lead to leaving pictures in unexpected places. I.e., we can not easily predict where the images will be stored on a given system when no path was set up manually.

Instead, I'd rather create a folder next to this.wallpaperlocation as the fallback. So basically here: ${xdg_cache_home}/${Self.metadata['uuid']}/favourites.

Copy link
Contributor Author

@Lucki Lucki Feb 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, although the cache folder is for data that can be regenerated and thus is unsuited.

Do you have a strong preference to one folder that's already included in this ugly chain?

  • $HOME/Pictures/…
  • $HOME/Downloads/…
  • $XDG_DATA_HOME/…

Another possibility would be to disable the button if no folder was set manually.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] is for data that can be regenerated and thus is unsuited.
Hm

From those folders, I'd use $XDG_DATA_HOME, to be honest. I generally don't want to "pollute" personal directories of people without their permission (I hate it when software creates folders in random places that I expect to "own" :D). You just have to take care of the case that $XDG_DATA_HOME might not be set on all distributions.

Another possibility would be to disable the button if no folder was set manually.

This is also a valid option. We could also just hide the button entirely when no folder is set. Or alternatively, directly open the file chooser for the storage folder when none is set.

I just want to make sure, that we do not store the images in random places. I added an issue here so we can merge this PR for now.


favoritesFolder = favoritesFolder.get_child(Self.metadata['uuid']);

this._settings.set('favorites-folder', 'string', favoritesFolder.get_path());
}
}

_clearHistory() {
Expand Down Expand Up @@ -313,10 +338,29 @@ var WallpaperController = class {
return;
}

historyElement.path = path;
historyElement.id = historyId;
historyElement.name = String(historyId);
historyElement.id = `${historyElement.timestamp}_${historyElement.name}`; // timestamp ensures uniqueness

// Move file to unique naming
let sourceFile = Gio.File.new_for_path(path);
let targetFolder = sourceFile.get_parent();
let targetFile = targetFolder.get_child(historyElement.id);

try {
if (!sourceFile.move(targetFile, Gio.FileCopyFlags.NONE, null, null)) {
this.logger.warning('Failed copying unique image.');
return;
}
} catch (error) {
if (error === Gio.IOErrorEnum.EXISTS) {
this.logger.warning('Image already exists in location.');
return;
}
}
ifl0w marked this conversation as resolved.
Show resolved Hide resolved

historyElement.path = targetFile.get_path();

this._setBackground(path, () => {
this._setBackground(historyElement.path, () => {
// insert file into history
this._historyController.insert(historyElement);
this.currentWallpaper = this._getCurrentWallpaper();
Expand Down