@@ -13,6 +13,7 @@ gtk-sys = "^0.4.0" | |||
libpijul = "^0.7.3" | |||
log = "^0.3.8" | |||
base64 = "^0.6.0" | |||
chrono = "^0.4.0" | |||
[dependencies.gdk] | |||
features = [ | |||
@@ -60,9 +60,34 @@ | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
<child> | |||
<object class="GtkComboBoxText" id="branch_select"> | |||
<object class="GtkBox"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
<child> | |||
<object class="GtkLabel"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
<property name="label" translatable="yes">Branch</property> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
<property name="fill">True</property> | |||
<property name="padding">2</property> | |||
<property name="position">0</property> | |||
</packing> | |||
</child> | |||
<child> | |||
<object class="GtkComboBoxText" id="branch_select"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
<property name="fill">True</property> | |||
<property name="padding">2</property> | |||
<property name="position">1</property> | |||
</packing> | |||
</child> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
@@ -70,6 +95,56 @@ | |||
<property name="position">0</property> | |||
</packing> | |||
</child> | |||
<child> | |||
<object class="GtkSeparator"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
<property name="fill">True</property> | |||
<property name="position">1</property> | |||
</packing> | |||
</child> | |||
<child> | |||
<object class="GtkBox"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
<property name="margin_left">2</property> | |||
<child> | |||
<object class="GtkLabel"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">False</property> | |||
<property name="label" translatable="yes">Hash</property> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
<property name="fill">True</property> | |||
<property name="padding">2</property> | |||
<property name="position">0</property> | |||
</packing> | |||
</child> | |||
<child> | |||
<object class="GtkEntry" id="hash_entry"> | |||
<property name="visible">True</property> | |||
<property name="can_focus">True</property> | |||
<property name="editable">False</property> | |||
</object> | |||
<packing> | |||
<property name="expand">True</property> | |||
<property name="fill">True</property> | |||
<property name="padding">2</property> | |||
<property name="position">1</property> | |||
</packing> | |||
</child> | |||
</object> | |||
<packing> | |||
<property name="expand">True</property> | |||
<property name="fill">True</property> | |||
<property name="padding">2</property> | |||
<property name="position">2</property> | |||
</packing> | |||
</child> | |||
</object> | |||
<packing> | |||
<property name="expand">False</property> | |||
@@ -0,0 +1,20 @@ | |||
use gtk; | |||
use ui::gui::*; | |||
pub struct AppS { | |||
_cant_construct: (), | |||
pub gui: Gui, | |||
} | |||
impl AppS { | |||
pub fn new() -> Self { | |||
let builder = gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/ui/window.glade"))); | |||
return AppS { | |||
_cant_construct: (), | |||
gui: Gui::new(builder), | |||
} | |||
} | |||
} |
@@ -9,6 +9,7 @@ extern crate gtk; | |||
extern crate gtk_sys; | |||
extern crate libpijul; | |||
extern crate base64; | |||
extern crate chrono; | |||
extern crate flexi_logger; | |||
#[macro_use] | |||
@@ -17,9 +18,12 @@ extern crate log; | |||
#[macro_use] | |||
extern crate error_chain; | |||
mod ui; | |||
mod appstate; | |||
mod errors; | |||
mod pijul_glue; | |||
mod ui; | |||
use appstate::*; | |||
use ui::entry::*; | |||
use std::rc::Rc; | |||
@@ -0,0 +1,18 @@ | |||
use libpijul; | |||
use libpijul::fs_representation::*; | |||
use errors::*; | |||
pub fn get_branches(path: &str) -> Result<Vec<String>> { | |||
let mut vec = Vec::new(); | |||
let repo = libpijul::Repository::open(pristine_dir(path), None).unwrap(); | |||
let txn = repo.txn_begin().unwrap(); | |||
let branches = txn.iter_branches(None).map(|x| String::from(x.name.as_str())); | |||
vec.extend(branches); | |||
return Ok(vec); | |||
} | |||
@@ -0,0 +1,4 @@ | |||
//! The pijul glue subsystem. | |||
pub mod branches; | |||
pub mod patches; |
@@ -0,0 +1,79 @@ | |||
use libpijul; | |||
use libpijul::patch; | |||
use libpijul::fs_representation::*; | |||
use std::path::Path; | |||
use errors::*; | |||
use base64; | |||
pub struct Patch { | |||
pub name: String, | |||
pub authors: String, | |||
pub created: String, | |||
pub signed: bool, | |||
hash: libpijul::Hash, | |||
} | |||
impl Patch { | |||
pub fn from_patch(patch: patch::Patch, hash: libpijul::Hash) -> Self { | |||
let p = Patch { | |||
name: patch.header().name.clone(), | |||
authors: patch.header().authors.join(", "), | |||
created: format!("{}", patch.header().timestamp.format("%F %T")), | |||
signed: patch_is_signed(patch), | |||
hash: hash, | |||
}; | |||
return p; | |||
} | |||
pub fn hash_to_str(&self) -> String { | |||
let config = base64::Config::new(base64::CharacterSet::Standard, | |||
false, | |||
false, | |||
base64::LineWrap::NoWrap); | |||
let s = self.hash.to_base64(config); | |||
return s; | |||
} | |||
} | |||
pub fn str_to_hash(s: String) -> Option<libpijul::Hash> { | |||
return libpijul::Hash::from_base64(s.as_ref()); | |||
} | |||
pub fn get_patch_headers(path: &str, branch: &str) -> Result<Vec<Patch>> { | |||
let mut vec = Vec::new(); | |||
let repo = libpijul::Repository::open(pristine_dir(path), None)?; | |||
let txn = repo.txn_begin().unwrap(); | |||
let branch = txn.get_branch(branch).unwrap(); | |||
let patches_ids = txn.iter_patches(&branch, None).map(|x| x.0); | |||
for pid in patches_ids { | |||
let hash_ref = txn.external_hash(pid); | |||
let mp = libpijul::fs_representation::read_patch(Path::new(path), | |||
hash_ref); | |||
match mp { | |||
Ok(p) => { | |||
vec.push(Patch::from_patch(p, hash_ref.to_owned())); | |||
}, | |||
Err(e) => warn!("Could not get patch for patch_id {:?}\nError: {:?}", pid, e), | |||
} | |||
} | |||
return Ok(vec); | |||
} | |||
pub fn patch_is_signed(patch: patch::Patch) -> bool { | |||
match patch { | |||
patch::Patch::Unsigned(_) => return false, | |||
patch::Patch::Signed{patch: _, signature: _ } => return true, | |||
} | |||
} | |||
@@ -1,48 +1,14 @@ | |||
use gtk; | |||
use appstate::*; | |||
use gtk::WidgetExt; | |||
use gtk::prelude::*; | |||
use gtk; | |||
use pijul_glue::branches::*; | |||
use pijul_glue::patches::*; | |||
use std::rc::Rc; | |||
use gtk::WidgetExt; | |||
use libpijul; | |||
use libpijul::fs_representation::*; | |||
use std::path::Path; | |||
use errors::*; | |||
pub struct AppS { | |||
_cant_construct: (), | |||
pub gui: Gui, | |||
} | |||
const HASH_COLUMN: i32 = 3; | |||
impl AppS { | |||
pub fn new() -> Self { | |||
let builder = gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/ui/window.glade"))); | |||
return AppS { | |||
_cant_construct: (), | |||
gui: Gui::new(builder), | |||
} | |||
} | |||
} | |||
pub struct Gui { | |||
_cant_construct: (), | |||
pub window: gtk::Window, | |||
pub branch_select: gtk::ComboBoxText, | |||
pub patch_tree: gtk::TreeView, | |||
} | |||
impl Gui { | |||
pub fn new(builder: gtk::Builder) -> Self { | |||
return Gui { | |||
_cant_construct: (), | |||
window: builder.get_object("main").unwrap(), | |||
branch_select: builder.get_object("branch_select").unwrap(), | |||
patch_tree: builder.get_object("patch_tree").unwrap(), | |||
} | |||
} | |||
} | |||
pub fn init(appstate: Rc<AppS>) { | |||
@@ -56,61 +22,74 @@ pub fn init(appstate: Rc<AppS>) { | |||
} | |||
{ | |||
let ls = gtk::ListStore::new(&[gtk::Type::String]); | |||
let patches = get_patches("/home/hasufell/git/pijul", | |||
let mut columns: Vec<gtk::TreeViewColumn> = Vec::new(); | |||
append_column("Patch", &mut columns, &appstate.gui.patch_tree, Some(200)); | |||
append_column("Authors", &mut columns, &appstate.gui.patch_tree, None); | |||
append_column("Date", &mut columns, &appstate.gui.patch_tree, None); | |||
let ls = gtk::ListStore::new(&[ | |||
// visible | |||
gtk::Type::String, // name | |||
gtk::Type::String, // authors | |||
gtk::Type::String, // creation date | |||
// not visible | |||
gtk::Type::String, // hash | |||
]); | |||
let patches = get_patch_headers("/home/hasufell/git/pijul", | |||
"master"); | |||
for patch in patches.unwrap() { | |||
ls.insert_with_values(None, &[0], &[&patch.as_str()]); | |||
ls.insert_with_values(None, &[0, 1, 2, 3], | |||
&[&patch.name.as_str(), | |||
&patch.authors.as_str(), | |||
&patch.created.as_str(), | |||
&patch.hash_to_str(), | |||
]); | |||
} | |||
appstate.gui.patch_tree.set_model(Some(&ls)); | |||
appstate.gui.patch_tree.set_headers_visible(true); | |||
} | |||
let renderer = gtk::CellRendererText::new(); | |||
let col = gtk::TreeViewColumn::new(); | |||
col.set_title("Patch"); | |||
col.set_resizable(true); | |||
col.pack_start(&renderer, true); | |||
col.add_attribute(&renderer, "text", 0); | |||
col.set_clickable(true); | |||
col.set_sort_column_id(0); | |||
let selection = appstate.gui.patch_tree.get_selection(); | |||
selection.set_mode(gtk::SelectionMode::Single); | |||
appstate.gui.patch_tree.append_column(&col); | |||
appstate.gui.patch_tree.set_model(Some(&ls)); | |||
/* connect selection change */ | |||
{ | |||
let apps = appstate.clone(); | |||
selection.connect_changed(move |ts| { | |||
if let Some((model, iter)) = ts.get_selected() { | |||
let val = model.get_value(&iter, HASH_COLUMN); | |||
let s: String = val.downcast().unwrap().get().unwrap(); | |||
apps.gui.hash_entry.set_text(s.as_ref()); | |||
} | |||
}); | |||
} | |||
appstate.gui.window.show_all(); | |||
} | |||
fn append_column(title: &str, | |||
v: &mut Vec<gtk::TreeViewColumn>, | |||
tree: >k::TreeView, | |||
max_width: Option<i32>) { | |||
fn get_branches(path: &str) -> Result<Vec<String>> { | |||
let mut vec = Vec::new(); | |||
let id = v.len() as i32; | |||
let repo = libpijul::Repository::open(pristine_dir(path), None).unwrap(); | |||
let txn = repo.txn_begin().unwrap(); | |||
let renderer = gtk::CellRendererText::new(); | |||
let col = gtk::TreeViewColumn::new(); | |||
col.set_title(title); | |||
col.set_resizable(true); | |||
col.set_sort_column_id(id); | |||
if let Some(max_width) = max_width { | |||
col.set_max_width(max_width); | |||
col.set_expand(true); | |||
} | |||
col.pack_start(&renderer, true); | |||
col.add_attribute(&renderer, "text", id); | |||
col.set_clickable(true); | |||
let branches = txn.iter_branches(None).map(|x| String::from(x.name.as_str())); | |||
tree.append_column(&col); | |||
vec.extend(branches); | |||
return Ok(vec); | |||
v.push(col); | |||
} | |||
fn get_patches(path: &str, branch: &str) -> Result<Vec<String>> { | |||
let mut vec = Vec::new(); | |||
let repo = libpijul::Repository::open(pristine_dir(path), None)?; | |||
let txn = repo.txn_begin().unwrap(); | |||
let branch = txn.get_branch(branch).unwrap(); | |||
let patches_ids = txn.iter_patches(&branch, None).map(|x| x.0); | |||
for pid in patches_ids { | |||
let mp = libpijul::fs_representation::read_patch(Path::new(path), | |||
txn.external_hash(pid)); | |||
match mp { | |||
Ok(p) => vec.push(p.header().name.clone()), | |||
Err(e) => warn!("Could not get patch for patch_id {:?}\nError: {:?}", pid, e), | |||
} | |||
} | |||
return Ok(vec); | |||
} |
@@ -0,0 +1,22 @@ | |||
use gtk; | |||
pub struct Gui { | |||
_cant_construct: (), | |||
pub window: gtk::Window, | |||
pub branch_select: gtk::ComboBoxText, | |||
pub patch_tree: gtk::TreeView, | |||
pub hash_entry: gtk::Entry, | |||
} | |||
impl Gui { | |||
pub fn new(builder: gtk::Builder) -> Self { | |||
return Gui { | |||
_cant_construct: (), | |||
window: builder.get_object("main").unwrap(), | |||
branch_select: builder.get_object("branch_select").unwrap(), | |||
patch_tree: builder.get_object("patch_tree").unwrap(), | |||
hash_entry: builder.get_object("hash_entry").unwrap(), | |||
} | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
//! The UI subsystem. | |||
pub mod entry; | |||
pub mod gui; |