diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/deamon.vala | 8 | ||||
-rw-r--r-- | src/gui/preferencesWindow.vala | 134 | ||||
-rw-r--r-- | src/gui/themeList.vala | 20 | ||||
-rw-r--r-- | src/gui/tipViewer.vala | 163 | ||||
-rwxr-xr-x | src/renderers/pieWindow.vala | 12 | ||||
-rw-r--r-- | src/themes/theme.vala | 36 | ||||
-rw-r--r-- | src/themes/themeImporter.vala | 62 | ||||
-rw-r--r-- | src/utilities/archiveReader.vala | 123 | ||||
-rw-r--r-- | src/utilities/archiveWriter.vala | 139 | ||||
-rw-r--r-- | src/utilities/config.vala | 35 |
10 files changed, 708 insertions, 24 deletions
diff --git a/src/deamon.vala b/src/deamon.vala index 8c84f3a..f4e1aeb 100644 --- a/src/deamon.vala +++ b/src/deamon.vala @@ -43,7 +43,7 @@ public class Deamon : GLib.Application { ///////////////////////////////////////////////////////////////////// public static int main(string[] args) { - version = "0.6.5"; + version = "0.6.6"; // disable overlay scrollbar --- hacky workaround for black / // transparent background @@ -192,10 +192,12 @@ public class Deamon : GLib.Application { } if (reset) { - if (GLib.FileUtils.remove(Paths.pie_config) == 0) + if (GLib.FileUtils.remove(Paths.pie_config) == 0) { message("Removed file \"%s\"", Paths.pie_config); - if (GLib.FileUtils.remove(Paths.settings) == 0) + } + if (GLib.FileUtils.remove(Paths.settings) == 0) { message("Removed file \"%s\"", Paths.settings); + } return true; } diff --git a/src/gui/preferencesWindow.vala b/src/gui/preferencesWindow.vala index 5d22d6b..d671501 100644 --- a/src/gui/preferencesWindow.vala +++ b/src/gui/preferencesWindow.vala @@ -154,6 +154,17 @@ public class PreferencesWindow : GLib.Object { scroll_area = builder.get_object("theme-scrolledwindow") as Gtk.ScrolledWindow; scroll_area.add(this.theme_list); + (builder.get_object("theme-help-button") as Gtk.Button).clicked.connect(() => { + try{ + GLib.AppInfo.launch_default_for_uri("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/", null); + } catch (Error e) { + warning(e.message); + } + }); + + (builder.get_object("theme-export-button") as Gtk.Button).clicked.connect(on_export_theme_button_clicked); + (builder.get_object("theme-import-button") as Gtk.Button).clicked.connect(on_import_theme_button_clicked); + this.autostart = (builder.get_object("autostart-checkbox") as Gtk.ToggleButton); this.autostart.toggled.connect(on_autostart_toggled); @@ -209,6 +220,33 @@ public class PreferencesWindow : GLib.Object { Config.global.max_visible_slices = (int)range_slices.get_value(); }); + var info_box = (builder.get_object("info-box") as Gtk.Box); + + // info label + var info_label = new TipViewer({ + _("Pies can be opened with the terminal command \"gnome-pie --open=ID\"."), + _("Feel free to visit Gnome-Pie's homepage at %s!").printf("<a href='http://simmesimme.github.io/gnome-pie.html'>gnome-pie.simonschneegans.de</a>"), + _("If you want to give some feedback, please write an e-mail to %s!").printf("<a href='mailto:code@simonschneegans.de'>code@simonschneegans.de</a>"), + _("You can support the development of Gnome-Pie by donating via %s.").printf("<a href='https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=X65SUVC4ZTQSC'>Paypal</a>"), + _("Translating Gnome-Pie to your language is easy. Translations are managed at %s.").printf("<a href='https://translate.zanata.org/zanata/iteration/view/gnome-pie/develop'>Zanata</a>"), + _("It's easy to create new themes for Gnome-Pie. Read the <a href='%s'>Tutorial</a> online.").printf("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/"), + _("It's usually a good practive to have at most twelve slices per pie."), + _("You can export themes you created and share them with the community!"), + _("The source code of Gnome-Pie is available on %s.").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"), + _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"), + _("Suggestions can be posted on %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"), + _("An awesome companion of Gnome-Pie is %s. It will make using your computer feel like magic!").printf("<a href='https://github.com/thjaeger/easystroke/wiki'>Easystroke</a>"), + _("You can drag'n'drop applications from your main menu to the pie above."), + _("You may drag'n'drop URLs and bookmarks from your internet browser to the pie above."), + _("You can drag'n'drop files and folders from your file browser to the pie above."), + _("You can drag'n'drop pies from the list on the left into other pies in order to create sub-pies."), + _("You can drag'n'drop pies from the list on the left to your desktop or dock to create a launcher for this pie.") + }); + this.window.show.connect(info_label.start_slide_show); + this.window.hide.connect(info_label.stop_slide_show); + + info_box.pack_end(info_label); + this.window.hide.connect(() => { // save settings on close Config.global.save(); @@ -290,15 +328,105 @@ public class PreferencesWindow : GLib.Object { FileUtils.set_contents(Paths.autostart, autostart_entry); FileUtils.chmod(Paths.autostart, 0755); } catch (Error e) { - var d = new Gtk.MessageDialog (this.window, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, + var d = new Gtk.MessageDialog(this.window, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, "%s", e.message); - d.run (); - d.destroy (); + d.run(); + d.destroy(); } } } ///////////////////////////////////////////////////////////////////// + /// Saves the current theme to an archive. + ///////////////////////////////////////////////////////////////////// + + private void on_export_theme_button_clicked(Gtk.Button button) { + var dialog = new Gtk.FileChooserDialog("Pick a file", this.window, + Gtk.FileChooserAction.SAVE, + "_Cancel", + Gtk.ResponseType.CANCEL, + "_Save", + Gtk.ResponseType.ACCEPT); + + dialog.set_do_overwrite_confirmation(true); + dialog.set_modal(true); + dialog.filter = new Gtk.FileFilter(); + dialog.filter.add_pattern ("*.tar.gz"); + dialog.set_current_name(Config.global.theme.name + ".tar.gz"); + + dialog.response.connect((d, result) => { + if (result == Gtk.ResponseType.ACCEPT) { + var file = dialog.get_filename(); + if (!file.has_suffix(".tar.gz")) { + file = file + ".tar.gz"; + } + Config.global.theme.export(file); + } + dialog.destroy(); + }); + dialog.show(); + } + + ///////////////////////////////////////////////////////////////////// + /// Imports a new theme from an archive. + ///////////////////////////////////////////////////////////////////// + + private void on_import_theme_button_clicked(Gtk.Button button) { + var dialog = new Gtk.FileChooserDialog("Pick a file", this.window, + Gtk.FileChooserAction.OPEN, + "_Cancel", + Gtk.ResponseType.CANCEL, + "_Open", + Gtk.ResponseType.ACCEPT); + + dialog.set_modal(true); + dialog.filter = new Gtk.FileFilter(); + dialog.filter.add_pattern ("*.tar.gz"); + + var result = Gtk.MessageType.INFO; + var message = _("Sucessfully imported new theme!"); + + dialog.response.connect((d, r) => { + if (r == Gtk.ResponseType.ACCEPT) { + var file = dialog.get_filename(); + + var a = new ThemeImporter(); + if (a.open(file)) { + if (a.is_valid_theme) { + if (!Config.global.has_theme(a.theme_name)) { + if (a.extract_to(Paths.local_themes + "/" + a.theme_name)) { + Config.global.load_themes(a.theme_name); + this.theme_list.reload(); + } else { + message = _("An error occured while importing the theme: Failed to extract theme!"); + result = Gtk.MessageType.ERROR; + } + } else { + message = _("An error occured while importing the theme: A theme with this name does already exist!"); + result = Gtk.MessageType.ERROR; + } + } else { + message = _("An error occured while importing the theme: Theme archive does not contain a valid theme!"); + result = Gtk.MessageType.ERROR; + } + } else { + message = _("An error occured while importing the theme: Failed to open theme archive!"); + result = Gtk.MessageType.ERROR; + } + a.close(); + + var result_dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL, + result, Gtk.ButtonsType.CLOSE, message); + result_dialog.run(); + result_dialog.destroy(); + } + dialog.destroy(); + + }); + dialog.show(); + } + + ///////////////////////////////////////////////////////////////////// /// Shows or hides the indicator. ///////////////////////////////////////////////////////////////////// diff --git a/src/gui/themeList.vala b/src/gui/themeList.vala index 517c6d5..46ae876 100644 --- a/src/gui/themeList.vala +++ b/src/gui/themeList.vala @@ -51,9 +51,6 @@ class ThemeList : Gtk.TreeView { public ThemeList() { GLib.Object(); - var data = new Gtk.ListStore(2, typeof(Gdk.Pixbuf), - typeof(string)); - this.set_model(data); this.set_headers_visible(true); this.set_grid_lines(Gtk.TreeViewGridLines.NONE); this.set_fixed_height_mode(true); @@ -79,7 +76,7 @@ class ThemeList : Gtk.TreeView { Gtk.TreeIter active; if (this.get_selection().get_selected(null, out active)) { Timeout.add(10, () => { - int index = int.parse(data.get_path(active).to_string()); + int index = int.parse(this.model.get_path(active).to_string()); Config.global.theme = Config.global.themes[index]; this.on_select_new(); @@ -91,6 +88,15 @@ class ThemeList : Gtk.TreeView { } }); + reload(); + } + + public void reload() { + + var data = new Gtk.ListStore(2, typeof(Gdk.Pixbuf), + typeof(string)); + this.set_model(data); + // load all themes into the list var themes = Config.global.themes; foreach(var theme in themes) { @@ -99,10 +105,10 @@ class ThemeList : Gtk.TreeView { data.set(current, DataPos.ICON, theme.preview_icon.to_pixbuf()); data.set(current, DataPos.NAME, GLib.Markup.escape_text(theme.name)+"\n" + "<span font-size='x-small'>" + GLib.Markup.escape_text(theme.description) - + " - <i>"+GLib.Markup.escape_text(_("By")+" "+theme.author) - + "</i></span>"); - if(theme == Config.global.theme) + + "</span>"); + if(theme == Config.global.theme) { get_selection().select_iter(current); + } } } } diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala new file mode 100644 index 0000000..e2158bd --- /dev/null +++ b/src/gui/tipViewer.vala @@ -0,0 +1,163 @@ +///////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011-2015 by Simon Schneegans +// +// This program 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. +// +// This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +///////////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A widget showing tips. The tips are beautifully faded in and out. +///////////////////////////////////////////////////////////////////////// + +public class TipViewer : Gtk.Label { + + ///////////////////////////////////////////////////////////////////// + /// Some settings tweaking the behavior of the TipViewer. + ///////////////////////////////////////////////////////////////////// + + private const double fade_time = 0.5; + private const double frame_rate = 20.0; + private const double base_delay = 3.0; + + ///////////////////////////////////////////////////////////////////// + /// False, if the playback of tips is stopped. + ///////////////////////////////////////////////////////////////////// + + private bool playing = false; + + ///////////////////////////////////////////////////////////////////// + /// An array containing all tips. + ///////////////////////////////////////////////////////////////////// + + private string[] tips; + + ///////////////////////////////////////////////////////////////////// + /// The index of the currently displayed tip. + ///////////////////////////////////////////////////////////////////// + + private int index = -1; + + ///////////////////////////////////////////////////////////////////// + /// The fading value. + ///////////////////////////////////////////////////////////////////// + + private AnimatedValue alpha; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes all members and sets the basic layout. + ///////////////////////////////////////////////////////////////////// + + public TipViewer(string[] tips) { + this.tips = tips; + + this.alpha = new AnimatedValue.linear(0.0, 1.0, fade_time); + + this.set_alignment (0.0f, 0.5f); + this.opacity = 0; + this.wrap = true; + this.valign = Gtk.Align.END; + this.set_use_markup(true); + + this.override_font(Pango.FontDescription.from_string("8")); + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the playback of tips. + ///////////////////////////////////////////////////////////////////// + + public void start_slide_show() { + if (!this.playing && tips.length > 1) { + this.playing = true; + show_tip(); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Stops the playback of tips. + ///////////////////////////////////////////////////////////////////// + + public void stop_slide_show() { + this.playing = false; + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the fading in. + ///////////////////////////////////////////////////////////////////// + + private void fade_in() { + this.alpha = new AnimatedValue.linear(this.alpha.val, 1.0, fade_time); + + GLib.Timeout.add((uint)(1000.0/frame_rate), () => { + this.alpha.update(1.0/frame_rate); + this.opacity = this.alpha.val; + + return (this.alpha.val != 1.0); + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the fading out. + ///////////////////////////////////////////////////////////////////// + + private void fade_out() { + this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, fade_time); + + GLib.Timeout.add((uint)(1000.0/frame_rate), () => { + this.alpha.update(1.0/frame_rate); + this.opacity = this.alpha.val; + + return (this.alpha.val != 0.0); + }); + } + + private void show_tip() { + + this.set_random_tip(); + + this.fade_in(); + + uint delay = (uint)(base_delay*1000.0) + tips[this.index].length*30; + + GLib.Timeout.add(delay, () => { + this.fade_out(); + + if (this.playing) { + GLib.Timeout.add((uint)(1000.0*fade_time), () => { + this.show_tip(); + return false; + }); + } + + return false; + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Chooses the next random tip. + ///////////////////////////////////////////////////////////////////// + + private void set_random_tip() { + if (tips.length > 1) { + int next_index = -1; + do { + next_index = GLib.Random.int_range(0, tips.length); + } while (next_index == this.index); + this.index = next_index; + this.label = tips[this.index]; + } + } +} + +} diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala index a49813a..c1d70b7 100755 --- a/src/renderers/pieWindow.vala +++ b/src/renderers/pieWindow.vala @@ -149,10 +149,12 @@ public class PieWindow : Gtk.Window { // remember last pressed key in order to disable key repeat uint last_key = 0; + uint32 last_time_stamp = 0; this.key_press_event.connect((e) => { if (e.keyval != last_key) { - last_key = e.keyval; - this.handle_key_press(e.keyval, e.time, e.str); + this.handle_key_press(e.keyval, e.time, last_time_stamp, e.str); + last_key = e.keyval; + last_time_stamp = e.time; } return true; }); @@ -436,7 +438,11 @@ public class PieWindow : Gtk.Window { /// Do some useful stuff when keys are pressed. ///////////////////////////////////////////////////////////////////// - private void handle_key_press(uint key, uint32 time_stamp, string text) { + private void handle_key_press(uint key, uint32 time_stamp, uint32 last_time_stamp, string text) { + if (last_time_stamp + 1000 < time_stamp) { + this.search_string = ""; + } + if (Gdk.keyval_name(key) == "Escape") this.cancel(); else if (Gdk.keyval_name(key) == "Return") this.activate_slice(time_stamp); else if (!PieManager.get_is_turbo(this.renderer.id)) { diff --git a/src/themes/theme.vala b/src/themes/theme.vala index a7dd4f8..98e8994 100644 --- a/src/themes/theme.vala +++ b/src/themes/theme.vala @@ -115,6 +115,42 @@ public class Theme : GLib.Object { return true; } + + ///////////////////////////////////////////////////////////////////// + /// Exports the theme directory to an importable archive. + ///////////////////////////////////////////////////////////////////// + + public void export(string file) { + + var archive = new ArchiveWriter(); + bool success = true; + + if (!archive.open(file)) { + warning("Cannot open file " + file + " for writing!"); + success = false; + } else if (!archive.add(this.directory)) { + warning("Cannot append directory " + this.directory + " to archive!"); + success = false; + } + + archive.close(); + + if (success) { + var message = _("Successfully exported the theme \"%s\"!").printf(this.name); + var dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL, + Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, message); + dialog.run(); + dialog.destroy(); + + } else { + var message = _("An error occured while exporting the theme \"%s\"! Please check the console output.").printf(this.name); + var dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL, + Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, message); + dialog.run(); + dialog.destroy(); + } + } + ///////////////////////////////////////////////////////////////////// /// Loads all images of the theme. ///////////////////////////////////////////////////////////////////// diff --git a/src/themes/themeImporter.vala b/src/themes/themeImporter.vala new file mode 100644 index 0000000..f110696 --- /dev/null +++ b/src/themes/themeImporter.vala @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011-2015 by Simon Schneegans +// +// This program 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. +// +// This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +///////////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// This class provides functions to check whether an archive contains a +/// valid Gnome-Pie theme. +///////////////////////////////////////////////////////////////////////// + +public class ThemeImporter : ArchiveReader { + + public bool is_valid_theme; + public string theme_name; + + ///////////////////////////////////////////////////////////////////// + /// Returns + ///////////////////////////////////////////////////////////////////// + + public new bool open(string path) { + + this.is_valid_theme = false; + this.theme_name = ""; + + var tmp_reader = new ArchiveReader(); + + if (tmp_reader.open(path)) { + try { + var tmp_dir = GLib.DirUtils.make_tmp("gnomepieXXXXXX"); + if (tmp_reader.extract_to(tmp_dir)) { + var tmp_theme = new Theme(tmp_dir); + if (tmp_theme.load()) { + is_valid_theme = true; + theme_name = tmp_theme.name; + } + } + } catch (Error e) { + warning(e.message); + } + } + + tmp_reader.close(); + + return base.open(path); + } +} + +} diff --git a/src/utilities/archiveReader.vala b/src/utilities/archiveReader.vala new file mode 100644 index 0000000..16e4541 --- /dev/null +++ b/src/utilities/archiveReader.vala @@ -0,0 +1,123 @@ +///////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011-2015 by Simon Schneegans +// +// This program 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. +// +// This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +///////////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// This class can be used to unpack an archive to a directory. +///////////////////////////////////////////////////////////////////////// + +public class ArchiveReader : GLib.Object { + + private Archive.Read archive; + private Archive.WriteDisk writer; + + ///////////////////////////////////////////////////////////////////// + /// Constructs a new ArchiveReader + ///////////////////////////////////////////////////////////////////// + + public ArchiveReader() { + this.archive = new Archive.Read(); + this.archive.support_format_all(); + this.archive.support_filter_all(); + + this.writer = new Archive.WriteDisk(); + this.writer.set_options( + Archive.ExtractFlags.TIME | + Archive.ExtractFlags.PERM | + Archive.ExtractFlags.ACL | + Archive.ExtractFlags.FFLAGS + ); + this.writer.set_standard_lookup(); + } + + ///////////////////////////////////////////////////////////////////// + /// Call this once after you created the ArchiveReader. Pass the + /// path to the target archive location. + ///////////////////////////////////////////////////////////////////// + + public bool open(string path) { + return this.archive.open_filename(path, 10240) == Archive.Result.OK; + } + + ///////////////////////////////////////////////////////////////////// + /// Extracts all files from the previously opened archive. + ///////////////////////////////////////////////////////////////////// + + public bool extract_to(string directory) { + while (true) { + unowned Archive.Entry entry; + var r = this.archive.next_header(out entry); + + if (r == Archive.Result.EOF) { + break; + } + + if (r != Archive.Result.OK) { + warning(this.archive.error_string()); + return false; + } + + entry.set_pathname(directory + "/" + entry.pathname()); + + r = this.writer.write_header(entry); + + if (r != Archive.Result.OK) { + warning(this.writer.error_string()); + return false; + } + + if (entry.size() > 0) { + while (true) { + size_t offset, size; + void *buff; + + r = this.archive.read_data_block(out buff, out size, out offset); + if (r == Archive.Result.EOF) { + break; + } + + if (r != Archive.Result.OK) { + warning(this.archive.error_string()); + return false; + } + + this.writer.write_data_block(buff, size, offset); + } + } + + r = this.writer.finish_entry(); + + if (r != Archive.Result.OK) { + warning(this.writer.error_string()); + return false; + } + } + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// When all files have been added, close the directory again. + ///////////////////////////////////////////////////////////////////// + + public void close() { + this.archive.close(); + this.writer.close(); + } +} + +} diff --git a/src/utilities/archiveWriter.vala b/src/utilities/archiveWriter.vala new file mode 100644 index 0000000..92bd31b --- /dev/null +++ b/src/utilities/archiveWriter.vala @@ -0,0 +1,139 @@ +///////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011-2015 by Simon Schneegans +// +// This program 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. +// +// This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +///////////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// This class can be used to pack a directory of files recursively into +/// a *.tar.gz archive. +///////////////////////////////////////////////////////////////////////// + +public class ArchiveWriter : GLib.Object { + + private Archive.Write archive; + + ///////////////////////////////////////////////////////////////////// + /// Constructs a new ArchiveWriter + ///////////////////////////////////////////////////////////////////// + + public ArchiveWriter() { + this.archive = new Archive.Write(); + this.archive.add_filter_gzip(); + this.archive.set_format_pax_restricted(); + + } + + ///////////////////////////////////////////////////////////////////// + /// Call this once after you created the ArchiveWriter. Pass the + /// path to the target archive location. + ///////////////////////////////////////////////////////////////////// + + public bool open(string path) { + return this.archive.open_filename(path) == Archive.Result.OK; + } + + ///////////////////////////////////////////////////////////////////// + /// Adds all files of a given directory to the previously opened + /// archive. + ///////////////////////////////////////////////////////////////////// + + public bool add(string directory) { + return add_directory(directory, directory); + } + + ///////////////////////////////////////////////////////////////////// + /// When all files have been added, close the directory again. + ///////////////////////////////////////////////////////////////////// + + public void close() { + this.archive.close(); + } + + ///////////////////////////////////////////////////////////////////// + /// Private helper function which traveres a directory recursively. + ///////////////////////////////////////////////////////////////////// + + private bool add_directory(string directory, string relative_to) { + try { + var d = Dir.open(directory); + string name; + while ((name = d.read_name()) != null) { + string path = Path.build_filename(directory, name); + if (FileUtils.test(path, FileTest.IS_DIR)) { + if (!add_directory(path, relative_to)) { + return false; + } + + } else if (FileUtils.test(path, FileTest.IS_REGULAR)) { + if (!add_file(path, relative_to)) { + return false; + } + + } else { + warning("Packaging theme: Ignoring irregular file " + name); + } + } + } catch (Error e) { + warning (e.message); + return false; + } + + return true; + + } + + ///////////////////////////////////////////////////////////////////// + /// Private halper which adds a file to the archive. + ///////////////////////////////////////////////////////////////////// + + public bool add_file(string path, string relative_to) { + var entry = new Archive.Entry(); + entry.set_pathname(path.replace(relative_to, "")); + + Posix.Stat st; + Posix.stat(path, out st); + entry.copy_stat(st); + entry.set_size(st.st_size); + + if (this.archive.write_header(entry) == Archive.Result.OK) { + try { + var reader = File.new_for_path(path).read(); + uint8 buffer[4096]; + + var len = reader.read(buffer); + + while(len > 0) { + this.archive.write_data(buffer, len); + len = reader.read(buffer); + } + + this.archive.finish_entry(); + } catch (Error e) { + warning (e.message); + return false; + } + + } else { + warning("Failed to include file " + path + " into archive"); + return false; + } + + return true; + } +} + +} diff --git a/src/utilities/config.vala b/src/utilities/config.vala index 538602d..5dedddb 100644 --- a/src/utilities/config.vala +++ b/src/utilities/config.vala @@ -161,7 +161,9 @@ public class Config : GLib.Object { load_themes(theme_name); - if (error_occrured) save(); + if (error_occrured) { + save(); + } } ///////////////////////////////////////////////////////////////////// @@ -177,18 +179,19 @@ public class Config : GLib.Object { // load global themes var d = Dir.open(Paths.global_themes); while ((name = d.read_name()) != null) { - var theme = new Theme(Paths.global_themes + "/" + name); + var new_theme = new Theme(Paths.global_themes + "/" + name); - if (theme.load()) - themes.add(theme); + if (new_theme.load()) { + themes.add(new_theme); + } } // load local themes d = Dir.open(Paths.local_themes); while ((name = d.read_name()) != null) { - var theme = new Theme(Paths.local_themes + "/" + name); - if (theme.load()) - themes.add(theme); + var new_theme = new Theme(Paths.local_themes + "/" + name); + if (new_theme.load()) + themes.add(new_theme); } } catch (Error e) { @@ -211,10 +214,26 @@ public class Config : GLib.Object { warning("Theme \"" + current + "\" not found! Using fallback..."); } theme.load_images(); + } else { + error("No theme found!"); } - else error("No theme found!"); } + ///////////////////////////////////////////////////////////////////// + /// Returns true if a loaded theme has the given name or is in a + /// directory with the given name. + ///////////////////////////////////////////////////////////////////// + + public bool has_theme(string name) { + + foreach (var theme in themes) { + if (theme.name == name || theme.directory.has_suffix(name)) { + return true; + } + } + + return false; + } } } |