Commit 4718b5d0 authored by Bilal Elmoussaoui's avatar Bilal Elmoussaoui
Browse files

Preapre for a 0.0.1 release

parent 8e5340b3
Pipeline #131416 passed with stages
in 10 minutes and 36 seconds
......@@ -8,8 +8,8 @@ variables:
BUNDLE: "banner-viewer-nightly.flatpak"
flatpak:
image: "registry.gitlab.gnome.org/gnome/gnome-runtime-images/rust_bundle:master"
extends: '.flatpak'
image: "docker.io/bilelmoussaoui/rust-nightly:master"
extends: '.flatpak'
variables:
MANIFEST_PATH: "build-aux/org.gnome.design.BannerViewerDevel.json"
FLATPAK_MODULE: "banner-viewer"
......
This diff is collapsed.
......@@ -6,8 +6,8 @@ edition = "2018"
[dependencies]
gtk = { version = "0.7", features = ["v3_22"] }
glib = "0.8"
gio = { version = "0.7", features = ["v2_46"] }
glib = { version = "0.8", features = ["futures"] }
gio = { version = "0.7", features = ["v2_56", "futures"] }
gdk = "0.11"
pango = "0.7"
log = "0.4"
......@@ -18,4 +18,6 @@ libxml = "0.2"
sourceview = "0.7"
regex = "1.2"
lazy_static = "1.3"
reqwest = "0.9"
failure = "0.1"
reqwest = { version = "=0.10.0-alpha.1" }
tokio = "0.2.0-alpha.6"
......@@ -10,7 +10,7 @@
View and edit GNOME Software banners
</shortdesc>
<description xml:lang="en">
View and edit GNOME Software banners with live changes.
Banner Viewer allows distributors to view and edit GNOME Software banners.
</description>
<bug-database rdf:resource="https://gitlab.gnome.org/World/design/banner-viewer" />
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
......
......@@ -4,7 +4,7 @@
"runtime-version" : "master",
"sdk" : "org.gnome.Sdk",
"sdk-extensions" : [
"org.freedesktop.Sdk.Extension.rust-stable"
"org.freedesktop.Sdk.Extension.rust-nightly"
],
"command" : "banner-viewer",
"tags" : [
......@@ -15,7 +15,6 @@
"--filesystem=home",
"--filesystem=xdg-run/dconf",
"--filesystem=~/.config/dconf:ro",
"--filesystem=xdg-music",
"--talk-name=ca.desrt.dconf",
"--env=DCONF_USER_CONFIG_DIR=.config/dconf",
"--share=ipc",
......@@ -25,12 +24,12 @@
"--share=network"
],
"build-options" : {
"append-path" : "/usr/lib/sdk/rust-stable/bin",
"append-path" : "/usr/lib/sdk/rust-nightly/bin",
"build-args" : [
"--share=network"
],
"env" : {
"RUSTFLAGS" : "--remap-path-prefix =../ --error-format=human",
"RUSTFLAGS" : "--remap-path-prefix =../",
"CARGO_HOME" : "/run/build/banner-viewer/cargo",
"RUST_BACKTRACE" : "1",
"RUST_LOG" : "banner_viewer=info"
......
......@@ -31,7 +31,7 @@ appdata_conf.set('app-id', application_id)
appdata_conf.set('gettext-package', gettext_package)
appdata_file = i18n.merge_file (
input: configure_file(
input: 'org.gnome.design.BannerViewer.appdata.xml.in.in',
input: 'org.gnome.design.BannerViewer.metainfo.xml.in.in',
output: '@BASENAME@',
configuration: appdata_conf
),
......@@ -45,7 +45,7 @@ if appstream_util.found()
test(
'validate-appdata', appstream_util,
args: [
'validate-relax', '--nonet', appdata_file.full_path()
'validate', '--nonet', appdata_file.full_path()
]
)
endif
......
......@@ -7,7 +7,7 @@
<name>Banner Viewer</name>
<summary>View and edit GNOME Software banners.</summary>
<description>
<p>Banner Viewer allows GNOME Designers to view and edit GNOME Software banners with live changes.</p>
<p>Banner Viewer allows distributors to view and edit GNOME Software banners.</p>
</description>
<screenshots>
<screenshot type="default">
......@@ -20,7 +20,11 @@
<url type="donation">https://liberapay.com/bielmoussaoui</url>
<content_rating type="oars-1.0" />
<releases>
<release version="0.0.1" date="2019-07-11" />
<release version="0.0.1" date="2019-01-16">
<description>
First Release!
</description>
</release>
</releases>
<kudos>
<!--
......
use crate::banners;
use crate::config;
use crate::utils;
use crate::widgets::{View, Window};
use gio::prelude::*;
use gtk::prelude::*;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::copy;
use std::thread;
use std::path::PathBuf;
use std::{cell::RefCell, rc::Rc};
use glib::{Receiver, Sender};
pub enum Action {
OpenFile,
LoadFile(String),
ViewEmpty,
ViewBanners,
LoadFile(PathBuf),
SetView(View),
AddError(String, String), // (id, error)
RemoveError(String), // id
DownloadResource(String, String), // (url, cache_filepath)
......@@ -39,13 +37,10 @@ impl Application {
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let receiver = RefCell::new(Some(r));
let banners = banners::Banners::new(sender.clone());
let window = Window::new(sender.clone());
let builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/shortcuts.ui");
let dialog: gtk::ShortcutsWindow = builder.get_object("shortcuts").unwrap();
window.widget.set_help_overlay(Some(&dialog));
let application = Rc::new(Self {
app,
window,
......@@ -77,24 +72,19 @@ impl Application {
fn setup_gactions(&self) {
// Quit
let app = self.app.clone();
let simple_action = gio::SimpleAction::new("quit", None);
simple_action.connect_activate(move |_, _| app.quit());
self.app.add_action(&simple_action);
self.app.set_accels_for_action("app.quit", &["<primary>q"]);
action!(self.app, "quit", move |_, _| app.quit());
// About
let window = self.window.widget.clone();
let simple_action = gio::SimpleAction::new("about", None);
simple_action.connect_activate(move |_, _| {
action!(self.app, "about", move |_, _| {
let builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/about_dialog.ui");
let about_dialog: gtk::AboutDialog = builder.get_object("about_dialog").unwrap();
get_widget!(builder, gtk::AboutDialog, about_dialog);
about_dialog.set_transient_for(Some(&window));
about_dialog.connect_response(|dialog, _| dialog.destroy());
about_dialog.show();
});
self.app.add_action(&simple_action);
// Shortcuts
self.app.set_accels_for_action("app.quit", &["<primary>q"]);
self.app.set_accels_for_action("window.open", &["<primary>o"]);
self.app.set_accels_for_action("win.show-help-overlay", &["<primary>question"]);
}
......@@ -106,12 +96,18 @@ impl Application {
app.add_window(&window);
window.present();
});
// Shortcuts Dialog
let builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/shortcuts.ui");
get_widget!(builder, gtk::ShortcutsWindow, shortcuts);
self.window.widget.set_help_overlay(Some(&shortcuts));
}
fn setup_css(&self) {
let p = gtk::CssProvider::new();
gtk::CssProvider::load_from_resource(&p, "/org/gnome/design/BannerViewer/style.css");
gtk::StyleContext::add_provider_for_screen(&gdk::Screen::get_default().unwrap(), &p, 500);
if let Some(screen) = gdk::Screen::get_default() {
gtk::StyleContext::add_provider_for_screen(&screen, &p, 500);
}
}
fn open_file(&self) {
......@@ -129,16 +125,15 @@ impl Application {
if gtk::ResponseType::from(open_dialog.run()) == gtk::ResponseType::Accept {
let file = open_dialog.get_filename().unwrap();
self.sender.send(Action::LoadFile(file.to_str().unwrap().to_string())).unwrap();
send!(self.sender, Action::LoadFile(file));
};
open_dialog.destroy();
}
fn load_file(&self, filepath: String) {
fn load_file(&self, filepath: PathBuf) {
let file = gio::File::new_for_path(&filepath);
self.sender.send(Action::ViewEmpty).unwrap();
self.banners.load_file(file);
self.sender.send(Action::ViewBanners).unwrap();
self.window.load_banners(&self.banners);
}
fn do_action(&self, action: Action) -> glib::Continue {
......@@ -148,13 +143,7 @@ impl Application {
self.load_file(file.clone());
self.window.set_loaded_file(file);
}
Action::ViewEmpty => {
self.window.set_view(View::Empty);
}
Action::ViewBanners => {
self.window.load_banners(&self.banners);
self.window.set_view(View::Banners);
}
Action::SetView(view) => self.window.set_view(view),
Action::AddError(id, error) => {
self.errors.borrow_mut().insert(id, error);
self.window.set_errors(self.errors.borrow());
......@@ -163,21 +152,22 @@ impl Application {
self.errors.borrow_mut().remove(&id);
self.window.set_errors(self.errors.borrow());
}
Action::DownloadResource(resource_url, cache_file) => {
let sender = self.sender.clone();
thread::spawn(move || match reqwest::get(&resource_url) {
Ok(mut response) => {
let mut dest = File::create(cache_file).expect("Failed to create dest file");
copy(&mut response, &mut dest).expect("Couldn't download the file");
sender.send(Action::RemoveError(resource_url)).unwrap();
}
Err(err) => {
warn!("Failed to download the file {}", err);
sender.send(Action::AddError(resource_url, "Failed to download".to_string())).unwrap();
}
});
}
Action::DownloadResource(resource_url, cache_file) => self.download_into(resource_url, cache_file),
};
glib::Continue(true)
}
fn download_into(&self, resource_url: String, cache_file: String) {
let sender = self.sender.clone();
let ctx = glib::MainContext::default();
ctx.spawn(async move {
match utils::download(&resource_url, &cache_file).await {
Ok(_) => send!(sender, Action::RemoveError(resource_url)),
Err(err) => {
warn!("Failed to download the file {}", err);
send!(sender, Action::AddError(resource_url, "Failed to download".to_string()));
}
};
});
}
}
......@@ -7,7 +7,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::{cell::RefCell, rc::Rc};
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct Banner {
pub id: String,
pub css: String,
......
......@@ -3,18 +3,11 @@ extern crate lazy_static;
extern crate pretty_env_logger;
#[macro_use]
extern crate log;
extern crate gio;
extern crate glib;
extern crate gtk;
extern crate libhandy;
extern crate libxml;
extern crate pango;
extern crate regex;
extern crate reqwest;
extern crate sourceview;
use gettextrs::*;
#[macro_use]
mod utils;
mod application;
mod banners;
mod config;
......@@ -25,7 +18,8 @@ mod window_state;
use application::Application;
use config::{GETTEXT_PACKAGE, LOCALEDIR};
fn main() {
#[tokio::main]
async fn main() {
pretty_env_logger::init();
gtk::init().expect("Unable to start GTK3");
......
......@@ -44,6 +44,7 @@ sources = files(
'config.rs',
'main.rs',
'static_resources.rs',
'utils.rs',
'window_state.rs',
)
......
use std::fs::File;
use std::io::Write;
// Stolen from Shortwave
macro_rules! get_widget {
($builder:expr, $wtype:ty, $name:ident) => {
let $name: $wtype = $builder.get_object(stringify!($name)).expect(&format!("Could not find widget \"{}\"", stringify!($name)));
};
}
macro_rules! send {
($sender:expr, $action:expr) => {
match $sender.send($action) {
Err(err) => error!("Failed to send \"{}\" action due to {}", stringify!($action), err),
_ => (),
};
};
}
macro_rules! action {
($widget:expr, $name:expr, $callback:expr) => {
let simple_action = gio::SimpleAction::new($name, None);
simple_action.connect_activate($callback);
$widget.add_action(&simple_action);
};
}
// Source: https://github.com/gtk-rs/examples/
// make moving clones into closures more convenient
macro_rules! clone {
(@param _) => ( _ );
(@param $x:ident) => ( $x );
($($n:ident),+ => move || $body:expr) => (
{
$( let $n = $n.clone(); )+
move || $body
}
);
($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
{
$( let $n = $n.clone(); )+
move |$(clone!(@param $p),)+| $body
}
);
}
pub async fn download(url: &str, cache: &str) -> Result<(), failure::Error> {
let response = reqwest::get(url).await?;
let content = response.bytes().await?;
if !content.is_empty() {
let mut out = File::create(cache)?;
let mut data: Vec<u8> = content.as_ref().iter().cloned().collect();
out.write_all(&mut data)?;
}
Ok(())
}
......@@ -5,57 +5,69 @@ use gtk::prelude::*;
use sourceview::prelude::*;
use std::rc::Rc;
#[derive(Debug)]
pub struct Banner {
pub widget: gtk::Box,
builder: gtk::Builder,
sender: Sender<Action>,
banner: banners::Banner,
}
// Contains an EventBox & Revealer(SourceView)
impl Banner {
pub fn new(sender: Sender<Action>, banner: banners::Banner) -> Rc<Self> {
let builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/banner.ui");
let widget: gtk::Box = builder.get_object("banner_widget").unwrap();
get_widget!(builder, gtk::Box, banner_widget);
widget.get_style_context().add_class("banner-widget");
widget.show();
banner_widget.get_style_context().add_class("banner-widget");
banner_widget.show();
let b = Rc::new(Banner { widget, builder, sender, banner });
let b = Rc::new(Banner {
widget: banner_widget,
builder,
sender,
banner,
});
b.init(b.clone());
b
}
fn init(&self, banner_widget: Rc<Self>) {
let event_box: gtk::EventBox = self.builder.get_object("banner_eventbox").unwrap();
let code_revealer: gtk::Revealer = self.builder.get_object("source_revealer").unwrap();
let source_view: sourceview::View = self.builder.get_object("source_view").unwrap();
let source_buffer: sourceview::Buffer = self.builder.get_object("source_buffer").unwrap();
let undo_manager = source_buffer.get_undo_manager().unwrap();
get_widget!(self.builder, gtk::EventBox, banner_eventbox);
self.inject_css(&self.banner.css);
code_revealer.show();
get_widget!(self.builder, gtk::Revealer, source_revealer);
get_widget!(self.builder, sourceview::View, source_view);
source_view.set_monospace(true);
source_view.set_indent_width(2);
source_view.show();
event_box.show();
get_widget!(self.builder, sourceview::Buffer, source_buffer);
self.inject_css(&self.banner.css);
source_revealer.show();
banner_eventbox.show();
// Inject the CSS into the SourceView
let css_lang = sourceview::LanguageManager::get_default().map_or(None, |lm| lm.get_language("css")).unwrap();
source_buffer.set_language(Some(&css_lang));
source_buffer.set_highlight_syntax(true);
undo_manager.begin_not_undoable_action();
source_buffer.set_text(&self.banner.css.trim());
undo_manager.end_not_undoable_action();
source_view.set_monospace(true);
source_view.set_indent_width(2);
let scheme = sourceview::StyleSchemeManager::get_default().map_or(None, |scm| scm.get_scheme("solarized-light")).unwrap();
source_buffer.set_style_scheme(Some(&scheme));
if let Some(undo_manager) = source_buffer.get_undo_manager() {
undo_manager.begin_not_undoable_action();
undo_manager.end_not_undoable_action();
}
if let Some(scheme) = sourceview::StyleSchemeManager::get_default().map_or(None, |scm| scm.get_scheme("solarized-light")) {
source_buffer.set_style_scheme(Some(&scheme));
}
let parent = self.widget.clone();
event_box.connect_button_press_event(move |_, _| {
let is_revealed = code_revealer.get_reveal_child();
code_revealer.set_reveal_child(!is_revealed);
banner_eventbox.connect_button_press_event(move |_, _| {
let is_revealed = source_revealer.get_reveal_child();
source_revealer.set_reveal_child(!is_revealed);
if !is_revealed {
parent.get_style_context().add_class("revealed");
} else {
......@@ -74,7 +86,7 @@ impl Banner {
}
fn inject_css(&self, css: &str) {
let banner: gtk::Box = self.builder.get_object("banner").unwrap();
get_widget!(self.builder, gtk::Box, banner);
let ctx = banner.get_style_context();
// Inject the banner CSS
......@@ -88,11 +100,11 @@ impl Banner {
let banner_id = &self.banner.id;
let provider = gtk::CssProvider::new();
match provider.load_from_data(css.as_bytes()) {
Ok(_) => self.sender.send(Action::RemoveError(banner_id.to_string())).unwrap(),
Ok(_) => send!(self.sender, Action::RemoveError(banner_id.to_string())),
Err(err) => {
warn!("Couldn't load stylesheet of banner {}", err);
let sent_error = err.to_string().replace("<data>:", "");
self.sender.send(Action::AddError(banner_id.to_string(), sent_error)).unwrap();
send!(self.sender, Action::AddError(banner_id.to_string(), sent_error));
}
};
ctx.add_provider(&provider, 300);
......
......@@ -9,6 +9,7 @@ use gtk::prelude::*;
use libhandy::{Column, ColumnExt};
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::path::PathBuf;
pub enum View {
Empty,
......@@ -27,39 +28,43 @@ impl Window {
pub fn new(sender: Sender<Action>) -> Self {
let settings = gio::Settings::new(APP_ID);
let builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/window.ui");
let widget: gtk::ApplicationWindow = builder.get_object("window").unwrap();
get_widget!(builder, gtk::ApplicationWindow, window);
let banners_container = gtk::Box::new(gtk::Orientation::Vertical, 12);
if PROFILE == "Devel" {
widget.get_style_context().add_class("devel");
window.get_style_context().add_class("devel");
}
let window = Window {
widget,
let window_obj = Window {
widget: window,
sender,
builder,
banners_container,
settings: RefCell::new(settings),
};
window.init();
window.setup_actions();
window
window_obj.init();
window_obj.setup_actions();
window_obj
}
pub fn load_banners(&self, banners: &banners::Banners) {
if banners.elements.borrow().len() == 0 {
self.sender.send(Action::ViewEmpty).unwrap();
}
for elem in banners.elements.borrow().iter() {
let sender = self.sender.clone();
let banner_widget = banner::Banner::new(sender, elem.clone());
banner_widget.widget.show();
self.banners_container.pack_start(&banner_widget.widget, false, false, 18);
send!(self.sender, Action::SetView(View::Empty));
} else {
for elem in banners.elements.borrow().iter() {
let banner_widget = banner::Banner::new(self.sender.clone(), elem.clone());
banner_widget.widget.show();
self.banners_container.pack_start(&banner_widget.widget, false, false, 18);
}
send!(self.sender, Action::SetView(View::Banners));
}
}
pub fn set_view(&self, view: View) {
let main_stack: gtk::Stack = self.builder.get_object("main_stack").unwrap();
let headerbar: gtk::HeaderBar = self.builder.get_object("headerbar").unwrap();
get_widget!(self.builder, gtk::Stack, main_stack);
get_widget!(self.builder, gtk::HeaderBar, headerbar);
match view {
View::Empty => {
for child in self.banners_container.get_children() {
......@@ -75,19 +80,22 @@ impl Window {
}
}
pub fn set_loaded_file(&self, file: String) {
let headerbar: gtk::HeaderBar = self.builder.get_object("headerbar").unwrap();
headerbar.set_subtitle(Some(&file));
self.settings.borrow_mut().set_string("last-open-file", &file);
pub fn set_loaded_file(&self, file: PathBuf) {
get_widget!(self.builder, gtk::HeaderBar, headerbar);
let file_path = file.to_str().unwrap();
headerbar.set_subtitle(Some(&file_path));
self.settings.borrow_mut().set_string("last-open-file", &file_path);
}
pub fn set_errors(&self, errors: Ref<HashMap<String, String>>) {
let errors_togglebtn: gtk::ToggleButton = self.builder.get_object("errors_togglebtn").unwrap();
let errors_count_label: gtk::Label = self.builder.get_object("errors_count_label").unwrap();
get_widget!(self.builder, gtk::ToggleButton, errors_togglebtn);
get_widget!(self.builder, gtk::Label, errors_count_label);
let errors_count = errors.len();
if errors_count != 0 {
let errors_container: gtk::Box = self.builder.get_object("errors_container").unwrap();
get_widget!(self.builder, gtk::Box, errors_container);
// Empty the old errors
for error_label in errors_container.get_children() {
errors_container.remove(&error_label);
......@@ -123,10 +131,10 @@ impl Window {
fn init(&self) {
// setup app menu
let appmenu_btn: gtk::MenuButton = self.builder.get_object("appmenu_button").unwrap();
let menu_builder = gtk::Builder::new_from_resource("/org/gnome/design/BannerViewer/menu.ui");
let popover_menu: gio::MenuModel = menu_builder.get_object("popover_menu").unwrap();
appmenu_btn.set_menu_model(Some(&popover_menu));
get_widget!(menu_builder, gio::MenuModel, popover_menu