Company details (EIN, state number, address, every profile value), an entry's number and amount, an account's code and balance, a document's file name and SHA-256 copy on click: a pointer and copy icon on hover, a check for a moment after. Amounts copy as their exact plain decimal (fmt::plain: no "$", commas, or parentheses; "-" for negatives); where a click already drills down (report cells, register and transaction rows, general ledger lines), a right-click menu copies the amounts, the entry number, or the description. The sidebar's chain check copies the journal head and data folder. tests/copy.rs: every display style copies back to the exact value (property), and clicking the EIN, an entry's amount, and its number puts exactly that on the clipboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
8ac4e5438d
commit
eba85f9c77
10 files changed
+344
-17
No files matched your search
@@ -168,18 +168,22 @@ pub fn show_account(ui: &mut Ui, b: &Books, id: u64, shell: &mut Shell<'_>) {
|
||||
.range
|
||||
.unwrap_or((first.min(b.today), b.today));
|
||||
page(ui, "account", |ui| {
|
||||
ui.label(
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(format!("{:?} account · {}", a.account_type, a.code))
|
||||
.font(theme::regular(13.0))
|
||||
.color(p.muted),
|
||||
&a.code,
|
||||
);
|
||||
ui.label(RichText::new(&a.name).font(theme::bold(26.0)));
|
||||
ui.add_space(4.0);
|
||||
let bal = b.balance_on(id, b.today);
|
||||
ui.label(
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(format!("Balance today {}", fmt::dollars(bal)))
|
||||
.font(theme::regular(15.0))
|
||||
.color(p.muted),
|
||||
&fmt::plain(&fmt::dollars(bal)).unwrap_or_default(),
|
||||
);
|
||||
ui.add_space(14.0);
|
||||
let hist = balance_history(&b.ledger, &[id]);
|
||||
@@ -274,6 +278,25 @@ pub fn show_account(ui: &mut Ui, b: &Books, id: u64, shell: &mut Shell<'_>) {
|
||||
vec2(w, if sub.is_empty() { 30.0 } else { 42.0 }),
|
||||
Sense::click(),
|
||||
);
|
||||
resp.context_menu(|ui| {
|
||||
for (label, v) in [("Copy debit", d), ("Copy credit", c)] {
|
||||
if !v.is_zero() {
|
||||
widgets::copy_item(
|
||||
ui,
|
||||
label,
|
||||
&fmt::plain(&fmt::money(v)).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
widgets::copy_item(
|
||||
ui,
|
||||
"Copy balance",
|
||||
&fmt::plain(&fmt::money(bal)).unwrap_or_default(),
|
||||
);
|
||||
if let Some(id) = sub.strip_prefix('#').and_then(|t| t.split(' ').next()) {
|
||||
widgets::copy_item(ui, "Copy entry number", &format!("#{id}"));
|
||||
}
|
||||
});
|
||||
if resp.hovered() && !strong {
|
||||
ui.painter().rect_filled(r, CornerRadius::same(6), p.hover);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ fn kv(ui: &mut Ui, k: &str, v: &str) {
|
||||
// Narrow, the label goes above its value.
|
||||
if ui.available_width() < 360.0 {
|
||||
ui.label(RichText::new(k).font(theme::regular(12.0)).color(p.muted));
|
||||
ui.add(egui::Label::new(RichText::new(v).font(theme::regular(13.0))).wrap());
|
||||
widgets::copyable(ui, RichText::new(v).font(theme::regular(13.0)), v);
|
||||
ui.add_space(4.0);
|
||||
return;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ fn kv(ui: &mut Ui, k: &str, v: &str) {
|
||||
theme::regular(13.0),
|
||||
p.muted,
|
||||
);
|
||||
ui.add(egui::Label::new(RichText::new(v).font(theme::regular(13.0))).wrap());
|
||||
widgets::copyable(ui, RichText::new(v).font(theme::regular(13.0)), v);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,11 @@ fn detail(ui: &mut Ui, b: &Books, a: &Attachment, shell: &mut Shell<'_>) {
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.label(RichText::new(&a.file_name).font(theme::bold(20.0)));
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(&a.file_name).font(theme::bold(20.0)),
|
||||
&a.file_name,
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
egui::Grid::new(("docfacts", a.id))
|
||||
.num_columns(2)
|
||||
@@ -265,8 +269,10 @@ fn detail(ui: &mut Ui, b: &Books, a: &Attachment, shell: &mut Shell<'_>) {
|
||||
ui.label(fmt::timestamp(&a.created_at));
|
||||
ui.end_row();
|
||||
k(ui, "SHA-256");
|
||||
ui.add(
|
||||
egui::Label::new(RichText::new(&a.blob_sha).monospace().size(11.5)).wrap(),
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(&a.blob_sha).monospace().size(11.5),
|
||||
&a.blob_sha,
|
||||
);
|
||||
ui.end_row();
|
||||
if let Some(e) = a.entry_id {
|
||||
|
||||
@@ -24,10 +24,12 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
|
||||
.auto_shrink(false)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(format!("Entry #{}", e.id))
|
||||
.font(theme::regular(12.5))
|
||||
.color(p.muted),
|
||||
&format!("#{}", e.id),
|
||||
);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui
|
||||
@@ -106,7 +108,11 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
|
||||
ui.label(fmt::date(e.date));
|
||||
ui.end_row();
|
||||
k(ui, "Amount");
|
||||
ui.label(RichText::new(fmt::dollars(e.amount)).font(theme::semibold(14.0)));
|
||||
widgets::copyable(
|
||||
ui,
|
||||
RichText::new(fmt::dollars(e.amount)).font(theme::semibold(14.0)),
|
||||
&fmt::plain(&fmt::dollars(e.amount)).unwrap_or_default(),
|
||||
);
|
||||
ui.end_row();
|
||||
if let Some(c) = &created {
|
||||
k(ui, "Recorded");
|
||||
|
||||
@@ -1540,15 +1540,37 @@ fn render(ui: &mut Ui, t: &Table, pdf: &PdfInfo, shell: &mut Shell<'_>) {
|
||||
egui::pos2(x1 - col_w + 8.0, rr.top()),
|
||||
egui::pos2(x1, rr.bottom()),
|
||||
);
|
||||
let copy = fmt::plain(&text);
|
||||
let cresp = ui.interact(
|
||||
cell,
|
||||
ui.id().with(("cell", ri, i)),
|
||||
if c.drill.is_some() {
|
||||
if c.drill.is_some() || copy.is_some() {
|
||||
Sense::click()
|
||||
} else {
|
||||
Sense::hover()
|
||||
},
|
||||
);
|
||||
// An amount copies as its plain decimal: a click
|
||||
// where the cell doesn't drill down, the right-click
|
||||
// menu where it does.
|
||||
if let Some(v) = © {
|
||||
if c.drill.is_some() {
|
||||
cresp.context_menu(|ui| widgets::copy_item(ui, "Copy amount", v));
|
||||
} else {
|
||||
if cresp.hovered() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||
}
|
||||
if cresp.clicked() {
|
||||
widgets::copy_now(ui, cresp.id, v);
|
||||
}
|
||||
let note = if widgets::recently_copied(ui, cresp.id) {
|
||||
"Copied"
|
||||
} else {
|
||||
"Click to copy"
|
||||
};
|
||||
cresp.clone().on_hover_text(note);
|
||||
}
|
||||
}
|
||||
let hovered = c.drill.is_some() && cresp.hovered();
|
||||
let vcolor = if hovered { p.accent } else { color };
|
||||
let vfont = if matches!(r.kind, Kind::Line) {
|
||||
@@ -1677,6 +1699,15 @@ fn gl(ui: &mut Ui, b: &Books, s: NaiveDate, e: NaiveDate, shell: &mut Shell<'_>)
|
||||
color: egui::Color32|
|
||||
-> egui::Response {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, 24.0), Sense::click());
|
||||
resp.context_menu(|ui| {
|
||||
for v in &vals {
|
||||
if let Some(id) = v.strip_prefix('#') {
|
||||
widgets::copy_item(ui, "Copy entry number", &format!("#{id}"));
|
||||
} else if let Some(a) = fmt::plain(v) {
|
||||
widgets::copy_item(ui, &format!("Copy {v}"), &a);
|
||||
}
|
||||
}
|
||||
});
|
||||
if resp.hovered() && !strong {
|
||||
ui.painter().rect_filled(r, CornerRadius::same(4), p.hover);
|
||||
}
|
||||
|
||||
@@ -134,6 +134,22 @@ fn list(ui: &mut Ui, b: &Books, selected: Option<u64>, shell: &mut Shell<'_>) {
|
||||
fn row(ui: &mut Ui, b: &Books, e: &EntryInfo, on: bool, since_update: f64) -> egui::Response {
|
||||
let p = pal(ui.ctx());
|
||||
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 50.0), Sense::click());
|
||||
resp.context_menu(|ui| {
|
||||
widgets::copy_item(
|
||||
ui,
|
||||
"Copy amount",
|
||||
&fmt::plain(&fmt::money(e.amount)).unwrap_or_default(),
|
||||
);
|
||||
if !e.cash_effect.is_zero() {
|
||||
widgets::copy_item(
|
||||
ui,
|
||||
"Copy cash effect",
|
||||
&fmt::plain(&fmt::money(e.cash_effect)).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
widgets::copy_item(ui, "Copy entry number", &format!("#{}", e.id));
|
||||
widgets::copy_item(ui, "Copy description", &e.description);
|
||||
});
|
||||
let fresh = b.fresh.contains(&e.id) && since_update < 8.0;
|
||||
if on {
|
||||
ui.painter()
|
||||
|
||||
Reference in new issue
Block a user