280 lines
8.4 KiB
Rust
280 lines
8.4 KiB
Rust
use std::io::Write;
|
|
use std::process::{Command, Stdio};
|
|
use termion::{color, cursor, style};
|
|
use termion::raw::RawTerminal;
|
|
use crate::{Clock, Config, Layout, Position};
|
|
use crate::common::COLOR;
|
|
|
|
|
|
pub struct Countdown {
|
|
pub value: u32,
|
|
position: Option<Position>,
|
|
}
|
|
|
|
impl Countdown {
|
|
pub fn new() -> Countdown {
|
|
Countdown {
|
|
value: 0,
|
|
position: None,
|
|
}
|
|
}
|
|
|
|
pub fn reset(&mut self) {
|
|
self.value = 0;
|
|
}
|
|
|
|
// Draw countdown.
|
|
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>) {
|
|
if let Some(pos) = &self.position {
|
|
if self.value < 3600 {
|
|
// Show minutes and seconds.
|
|
write!(stdout,
|
|
"{}(-{:02}:{:02})",
|
|
cursor::Goto(pos.col, pos.line),
|
|
(self.value / 60) % 60,
|
|
self.value % 60)
|
|
.unwrap();
|
|
if self.value == 3599 {
|
|
// Write three additional spaces after switching from hour display to
|
|
// minute display.
|
|
write!(stdout, " ").unwrap();
|
|
}
|
|
} else {
|
|
// Show hours, minutes and seconds.
|
|
write!(stdout,
|
|
"{}(-{:02}:{:02}:{:02})",
|
|
cursor::Goto(pos.col, pos.line),
|
|
self.value / 3600,
|
|
(self.value / 60) % 60,
|
|
self.value % 60)
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Alarm {
|
|
time: u32,
|
|
display: String,
|
|
color_index: usize,
|
|
exceeded: bool,
|
|
}
|
|
|
|
impl Alarm {
|
|
fn reset(&mut self) {
|
|
self.exceeded = false;
|
|
}
|
|
}
|
|
|
|
pub struct AlarmRoster {
|
|
list: Vec<Alarm>,
|
|
}
|
|
|
|
impl AlarmRoster {
|
|
pub fn new() -> AlarmRoster {
|
|
AlarmRoster {
|
|
list: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn add(&mut self, buffer: &String)
|
|
-> Result<(), &'static str> {
|
|
|
|
let mut index = 0;
|
|
let mut time: u32 = 0;
|
|
|
|
// Parse input into seconds.
|
|
for sub in buffer.rsplit(':') {
|
|
if sub.len() > 0 {
|
|
let d = sub.parse::<u32>();
|
|
match d {
|
|
Ok(d) => time += d * 60u32.pow(index),
|
|
Err(_) => return Err("Could not parse number as <u32>."),
|
|
}
|
|
}
|
|
index += 1;
|
|
|
|
// More than 3 fields are an error.
|
|
if index > 3 { return Err("Too many colons to parse.") };
|
|
}
|
|
|
|
// Skip if time evaluated to zero.
|
|
if time == 0 { return Err("Evaluates to zero.") };
|
|
if time >= 24 * 60 * 60 { return Err("Values >24h not supported.") };
|
|
|
|
let alarm = Alarm {
|
|
display: buffer.clone(),
|
|
time,
|
|
color_index: (self.list.len() % COLOR.len()),
|
|
exceeded: false,
|
|
};
|
|
|
|
// Add to list, insert based on alarm time. Filter out double entries.
|
|
let mut i = self.list.len();
|
|
if i == 0 {
|
|
self.list.push(alarm);
|
|
} else {
|
|
while i > 0 {
|
|
// Filter out double entries.
|
|
if self.list[i - 1].time == time {
|
|
return Err("Already exists.");
|
|
} else if self.list[i - 1].time < time {
|
|
break;
|
|
}
|
|
i -= 1;
|
|
}
|
|
self.list.insert(i, alarm);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn pop(&mut self) -> Option<Alarm> {
|
|
self.list.pop()
|
|
}
|
|
|
|
// Check for exceeded alarms.
|
|
pub fn check(&mut self,
|
|
clock: &mut Clock,
|
|
layout: &Layout,
|
|
countdown: &mut Countdown) -> bool {
|
|
|
|
let mut hit = false;
|
|
let mut index = 0;
|
|
|
|
for alarm in &mut self.list {
|
|
// Ignore alarms already marked exceeded.
|
|
if !alarm.exceeded {
|
|
if alarm.time <= clock.elapsed {
|
|
// Found alarm to raise.
|
|
hit = true;
|
|
alarm.exceeded = true;
|
|
clock.color_index = Some(alarm.color_index);
|
|
countdown.value = 0;
|
|
countdown.position = None;
|
|
// Skip ahead to the next one.
|
|
index += 1;
|
|
continue;
|
|
}
|
|
// Reached the alarm to exceed next. Update countdown
|
|
// accordingly.
|
|
countdown.value = alarm.time - clock.elapsed;
|
|
if countdown.position.is_none() || layout.force_redraw {
|
|
// Compute position.
|
|
let mut col =
|
|
layout.roster.col
|
|
+ 3
|
|
+ alarm.display.len() as u16;
|
|
let mut line = layout.roster.line + index;
|
|
|
|
// Compensate for "hidden" items in the alarm roster.
|
|
// TODO: Make this more elegant and robust.
|
|
if let Some(offset) = (self.list.len() as u16)
|
|
.checked_sub(layout.roster_height + 1) {
|
|
|
|
if index <= offset{
|
|
// Draw next to placeholder ("[...]").
|
|
line = layout.roster.line;
|
|
col = layout.roster.col + 6;
|
|
} else {
|
|
line = line.checked_sub(offset).unwrap_or(layout.roster.line);
|
|
}
|
|
}
|
|
countdown.position = Some(Position { col, line, });
|
|
}
|
|
// Ignore other alarms.
|
|
break;
|
|
}
|
|
index += 1;
|
|
}
|
|
hit // Return value.
|
|
}
|
|
|
|
// Draw alarm roster according to layout.
|
|
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>, layout: &mut Layout) {
|
|
let mut width: u16 = 0;
|
|
let mut index = 0;
|
|
|
|
// Find first item to print in case we lack space to print them all.
|
|
// Final '-1' to take account for the input buffer.
|
|
let mut first = 0;
|
|
|
|
if self.list.len() > layout.roster_height as usize {
|
|
// Actually -1 (zero indexing) +1 (first line containing "...").
|
|
first = self.list.len() - layout.roster_height as usize;
|
|
index += 1;
|
|
|
|
write!(stdout,
|
|
"{}{}[...]{}",
|
|
cursor::Goto(layout.roster.col, layout.roster.line),
|
|
style::Faint,
|
|
style::Reset).unwrap();
|
|
}
|
|
|
|
for alarm in &self.list[first..] {
|
|
if alarm.exceeded {
|
|
write!(stdout,
|
|
"{}{} {}{} {}!{}",
|
|
cursor::Goto(layout.roster.col, layout.roster.line + index),
|
|
color::Bg(COLOR[alarm.color_index]),
|
|
color::Bg(color::Reset),
|
|
style::Bold,
|
|
alarm.display,
|
|
style::Reset)
|
|
.unwrap();
|
|
} else {
|
|
write!(stdout,
|
|
"{}{} {} {}",
|
|
cursor::Goto(layout.roster.col, layout.roster.line + index),
|
|
color::Bg(COLOR[alarm.color_index]),
|
|
color::Bg(color::Reset),
|
|
alarm.display)
|
|
.unwrap();
|
|
}
|
|
index += 1;
|
|
// Calculate roster width. Actual display width is 3 chars wider.
|
|
if 3 + alarm.display.len() as u16 > width {
|
|
width = 3 + alarm.display.len() as u16;
|
|
}
|
|
}
|
|
// Update layout information.
|
|
if layout.roster_width != width {
|
|
layout.roster_width = width;
|
|
layout.force_recalc = true;
|
|
}
|
|
}
|
|
|
|
// Reset every alarm.
|
|
pub fn reset_all(&mut self) {
|
|
for a in &mut self.list {
|
|
a.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute the command given on the command line.
|
|
pub fn alarm_exec(config: &Config, elapsed: u32) {
|
|
let mut args: Vec<String> = Vec::new();
|
|
let time: String;
|
|
|
|
if elapsed < 3600 {
|
|
time = format!("{:02}:{:02}", elapsed / 60, elapsed % 60);
|
|
} else {
|
|
time = format!("{:02}:{:02}:{:02}", elapsed /3600, (elapsed / 60) % 60, elapsed % 60);
|
|
}
|
|
|
|
// Replace every occurrence of "%s".
|
|
for s in config.alarm_exec.iter() {
|
|
args.push(s.replace("%s", &time));
|
|
}
|
|
|
|
if Command::new(
|
|
&config.alarm_exec[0])
|
|
.args(&args[1..])
|
|
.stdout(Stdio::null())
|
|
.stdin(Stdio::null())
|
|
.spawn().is_err() {
|
|
|
|
eprintln!("Error: Could not execute command");
|
|
}
|
|
}
|
|
|