Compare commits
2 Commits
dbcecd9352
...
806568cbde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
806568cbde | ||
|
|
19c7824a0e |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
/target/
|
/target/
|
||||||
|
/src/data/target/
|
||||||
|
/src/gui/target/
|
||||||
|
|||||||
601
Cargo.lock
generated
601
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -1,17 +1,5 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "gtimelog4"
|
members = [
|
||||||
version = "0.1.0"
|
"src/gui",
|
||||||
edition = "2021"
|
"src/data",
|
||||||
|
]
|
||||||
[dependencies]
|
|
||||||
gettext-rs = { version = "0.7", features = ["gettext-system"] }
|
|
||||||
gtk = { version = "0.4.8", package = "gtk4" }
|
|
||||||
rtimelog = { path = "/home/gunibert/Projekte/rtimelog" }
|
|
||||||
|
|
||||||
[dependencies.adw]
|
|
||||||
package = "libadwaita"
|
|
||||||
version = "0.2.0-alpha.2"
|
|
||||||
# features = ["v1_2"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
gtk = { version = "0.4.8", package = "gtk4" }
|
|
||||||
30
meson.build
30
meson.build
@ -10,9 +10,37 @@ gnome = import('gnome')
|
|||||||
|
|
||||||
|
|
||||||
subdir('data')
|
subdir('data')
|
||||||
subdir('src')
|
#subdir('src')
|
||||||
subdir('po')
|
subdir('po')
|
||||||
|
|
||||||
|
cargo_bin = find_program('cargo')
|
||||||
|
cargo_opt = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
|
||||||
|
cargo_opt += [ '--target-dir', meson.project_build_root() / 'src' ]
|
||||||
|
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ]
|
||||||
|
|
||||||
|
if get_option('buildtype') == 'release'
|
||||||
|
cargo_options += [ '--release' ]
|
||||||
|
rust_target = 'release'
|
||||||
|
else
|
||||||
|
rust_target = 'debug'
|
||||||
|
endif
|
||||||
|
|
||||||
|
cargo_build = custom_target(
|
||||||
|
'cargo-build',
|
||||||
|
build_by_default: true,
|
||||||
|
build_always_stale: true,
|
||||||
|
output: meson.project_name(),
|
||||||
|
console: true,
|
||||||
|
install: true,
|
||||||
|
install_dir: get_option('bindir'),
|
||||||
|
command: [
|
||||||
|
'env', cargo_env,
|
||||||
|
cargo_bin, 'build',
|
||||||
|
cargo_opt, '&&', 'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
gnome.post_install(
|
gnome.post_install(
|
||||||
glib_compile_schemas: true,
|
glib_compile_schemas: true,
|
||||||
gtk_update_icon_cache: true,
|
gtk_update_icon_cache: true,
|
||||||
|
|||||||
7
src/data/Cargo.lock
generated
Normal file
7
src/data/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data"
|
||||||
|
version = "0.1.0"
|
||||||
11
src/data/Cargo.toml
Normal file
11
src/data/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "data"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
diesel = { version = "2.0.2", features = ["chrono", "sqlite", "returning_clauses_for_sqlite_3_35"] }
|
||||||
|
diesel_migrations = "2.0.0"
|
||||||
|
chrono = "0.4.22"
|
||||||
BIN
src/data/data.db
Normal file
BIN
src/data/data.db
Normal file
Binary file not shown.
8
src/data/diesel.toml
Normal file
8
src/data/diesel.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "migrations"
|
||||||
0
src/data/migrations/.keep
Normal file
0
src/data/migrations/.keep
Normal file
3
src/data/migrations/2022-10-22-182216_initial/down.sql
Normal file
3
src/data/migrations/2022-10-22-182216_initial/down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
|
||||||
|
DROP TABLE entries;
|
||||||
8
src/data/migrations/2022-10-22-182216_initial/up.sql
Normal file
8
src/data/migrations/2022-10-22-182216_initial/up.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entries (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
start TIMESTAMP,
|
||||||
|
stop TIMESTAMP,
|
||||||
|
task TEXT
|
||||||
|
);
|
||||||
144
src/data/src/lib.rs
Normal file
144
src/data/src/lib.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use chrono::{Datelike, Duration, NaiveDateTime};
|
||||||
|
use diesel::{
|
||||||
|
AsChangeset, Connection, Identifiable, Insertable, Queryable, RunQueryDsl, SqliteConnection,
|
||||||
|
};
|
||||||
|
use diesel_migrations::MigrationHarness;
|
||||||
|
|
||||||
|
use schema::entries;
|
||||||
|
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
pub const MIGRATIONS: diesel_migrations::EmbeddedMigrations =
|
||||||
|
diesel_migrations::embed_migrations!();
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Queryable, Identifiable, AsChangeset)]
|
||||||
|
#[diesel(table_name = entries)]
|
||||||
|
pub struct Entry {
|
||||||
|
id: i32,
|
||||||
|
pub start: Option<NaiveDateTime>,
|
||||||
|
pub stop: Option<NaiveDateTime>,
|
||||||
|
pub task: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = entries)]
|
||||||
|
pub struct NewEntry {
|
||||||
|
start: Option<NaiveDateTime>,
|
||||||
|
stop: Option<NaiveDateTime>,
|
||||||
|
task: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Timelog {
|
||||||
|
conn: SqliteConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Timelog {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut conn = get_connection();
|
||||||
|
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
||||||
|
Self { conn }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timelog {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut conn = get_connection();
|
||||||
|
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
||||||
|
Self { conn }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry<P: AsRef<str>>(&mut self, task_name: P) -> Entry {
|
||||||
|
use schema::entries::dsl::*;
|
||||||
|
|
||||||
|
let now = chrono::Local::now().naive_local();
|
||||||
|
let mut start_time = None;
|
||||||
|
// update last entry
|
||||||
|
let mut today = self.get_today();
|
||||||
|
if let Some(last) = today.last_mut() {
|
||||||
|
start_time = last.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new entry
|
||||||
|
let entry = NewEntry {
|
||||||
|
start: start_time,
|
||||||
|
stop: Some(now),
|
||||||
|
task: Some(task_name.as_ref().to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::insert_into(entries)
|
||||||
|
.values(&entry)
|
||||||
|
.get_result::<Entry>(&mut self.conn)
|
||||||
|
.expect("Error saving entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entry(&mut self, item: &Entry) {
|
||||||
|
diesel::update(item)
|
||||||
|
.set(item)
|
||||||
|
.execute(&mut self.conn)
|
||||||
|
.expect("Could not update Entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_today(&mut self) -> Vec<Entry> {
|
||||||
|
let e: Vec<Entry> = entries::table
|
||||||
|
.load(&mut self.conn)
|
||||||
|
.expect("Could not load entries");
|
||||||
|
|
||||||
|
let now = chrono::Local::now();
|
||||||
|
let mut today_entries: Vec<_> = e
|
||||||
|
.into_iter()
|
||||||
|
.filter(|entry| {
|
||||||
|
if let Some(stop) = entry.stop {
|
||||||
|
if stop.day() == now.day() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
today_entries.sort_by_key(|entry| entry.stop);
|
||||||
|
|
||||||
|
return today_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_work_time_today(&mut self) -> Duration {
|
||||||
|
let entries_today = self.get_today();
|
||||||
|
|
||||||
|
let mut work = Duration::zero();
|
||||||
|
for entry in entries_today {
|
||||||
|
if let Some(task) = entry.task {
|
||||||
|
if !task.starts_with("**") {
|
||||||
|
if let (Some(start), Some(stop)) = (entry.start, entry.stop) {
|
||||||
|
let duration = stop - start;
|
||||||
|
work = work + duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
work
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_slack_time_today(&mut self) -> Duration {
|
||||||
|
let entries_today = self.get_today();
|
||||||
|
|
||||||
|
let mut slack = Duration::zero();
|
||||||
|
for entry in entries_today {
|
||||||
|
if let Some(task) = entry.task {
|
||||||
|
if task.contains("**") {
|
||||||
|
if let (Some(start), Some(stop)) = (entry.start, entry.stop) {
|
||||||
|
let duration = stop - start;
|
||||||
|
slack = slack + duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_connection() -> SqliteConnection {
|
||||||
|
let database_url = "sqlite://data.db";
|
||||||
|
SqliteConnection::establish(database_url)
|
||||||
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||||
|
}
|
||||||
10
src/data/src/schema.rs
Normal file
10
src/data/src/schema.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
entries (id) {
|
||||||
|
id -> Integer,
|
||||||
|
start -> Nullable<Timestamp>,
|
||||||
|
stop -> Nullable<Timestamp>,
|
||||||
|
task -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
1311
src/gui/Cargo.lock
generated
Normal file
1311
src/gui/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
src/gui/Cargo.toml
Normal file
21
src/gui/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "gtimelog4"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gettext-rs = { version = "0.7", features = ["gettext-system"] }
|
||||||
|
gtk = { version = "0.5.0", package = "gtk4" }
|
||||||
|
#rtimelog = { path = "/home/gunibert/Projekte/rtimelog" }
|
||||||
|
#rtimelog = { git = "https://github.com/martinpitt/rtimelog/"}
|
||||||
|
data = { path = "../data"}
|
||||||
|
chrono = "0.4.22"
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
|
||||||
|
[dependencies.adw]
|
||||||
|
package = "libadwaita"
|
||||||
|
version = "0.2.0"
|
||||||
|
features = ["v1_2"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
glib-build-tools = "0.16.0"
|
||||||
@ -1,7 +1,6 @@
|
|||||||
use gtk::gio;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
gio::compile_resources(
|
glib_build_tools::compile_resources(
|
||||||
"src",
|
"src",
|
||||||
"src/gtimelog4.gresource.xml",
|
"src/gtimelog4.gresource.xml",
|
||||||
"gtimelog4.gresource",
|
"gtimelog4.gresource",
|
||||||
@ -18,12 +18,12 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::config::VERSION;
|
||||||
|
use adw::subclass::prelude::*;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
|
||||||
use gtk::{gio, glib};
|
use gtk::{gio, glib};
|
||||||
|
|
||||||
use crate::config::VERSION;
|
|
||||||
use crate::Gtimelog4Window;
|
use crate::Gtimelog4Window;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
@ -40,13 +40,12 @@ mod imp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for Gtimelog4Application {
|
impl ObjectImpl for Gtimelog4Application {
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
fn constructed(&self) {
|
||||||
self.parent_constructed(obj);
|
self.parent_constructed();
|
||||||
|
let obj = self.instance();
|
||||||
|
|
||||||
obj.setup_gactions();
|
obj.setup_gactions();
|
||||||
obj.set_accels_for_action("app.quit", &["<primary>q"]);
|
obj.set_accels_for_action("app.quit", &["<primary>q"]);
|
||||||
|
|
||||||
let timelog = rtimelog::store::Timelog::new_from_default_file();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,12 +54,15 @@ mod imp {
|
|||||||
// has been launched. Additionally, this callback notifies us when the user
|
// has been launched. Additionally, this callback notifies us when the user
|
||||||
// tries to launch a "second instance" of the application. When they try
|
// tries to launch a "second instance" of the application. When they try
|
||||||
// to do that, we'll just present any existing window.
|
// to do that, we'll just present any existing window.
|
||||||
fn activate(&self, application: &Self::Type) {
|
fn activate(&self) {
|
||||||
|
let application = self.instance();
|
||||||
// Get the current window or create one if necessary
|
// Get the current window or create one if necessary
|
||||||
let window = if let Some(window) = application.active_window() {
|
let window = if let Some(window) = application.active_window() {
|
||||||
window
|
window
|
||||||
} else {
|
} else {
|
||||||
let window = Gtimelog4Window::new(application);
|
let timelog = data::Timelog::new();
|
||||||
|
let window = Gtimelog4Window::new(&*application);
|
||||||
|
window.set_timelog(timelog);
|
||||||
window.upcast()
|
window.upcast()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,8 +83,10 @@ glib::wrapper! {
|
|||||||
|
|
||||||
impl Gtimelog4Application {
|
impl Gtimelog4Application {
|
||||||
pub fn new(application_id: &str, flags: &gio::ApplicationFlags) -> Self {
|
pub fn new(application_id: &str, flags: &gio::ApplicationFlags) -> Self {
|
||||||
glib::Object::new(&[("application-id", &application_id), ("flags", flags)])
|
glib::Object::new::<Gtimelog4Application>(&[
|
||||||
.expect("Failed to create Gtimelog4Application")
|
("application-id", &application_id),
|
||||||
|
("flags", flags),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_gactions(&self) {
|
fn setup_gactions(&self) {
|
||||||
@ -101,16 +105,16 @@ impl Gtimelog4Application {
|
|||||||
|
|
||||||
fn show_about(&self) {
|
fn show_about(&self) {
|
||||||
let window = self.active_window().unwrap();
|
let window = self.active_window().unwrap();
|
||||||
// let about = adw::AboutWindow::builder()
|
let about = adw::AboutWindow::builder()
|
||||||
// .transient_for(&window)
|
.transient_for(&window)
|
||||||
// .application_name("gtimelog4")
|
.application_name("gtimelog4")
|
||||||
// .application_icon("de.gunibert.gtimelog4")
|
.application_icon("de.gunibert.gtimelog4")
|
||||||
// .developer_name("Günther Wagner")
|
.developer_name("Günther Wagner")
|
||||||
// .version(VERSION)
|
.version(VERSION)
|
||||||
// .developers(vec!["Günther Wagner".into()])
|
.developers(vec!["Günther Wagner".into()])
|
||||||
// .copyright("© 2022 Günther Wagner")
|
.copyright("© 2022 Günther Wagner")
|
||||||
// .build();
|
.build();
|
||||||
|
|
||||||
// about.present();
|
about.present();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
7
src/gui/src/bigger.css
Normal file
7
src/gui/src/bigger.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.data-table {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
header label {
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
pub static VERSION: &str = "0.1.0";
|
pub static VERSION: &str = "0.1.0";
|
||||||
pub static GETTEXT_PACKAGE: &str = "gtimelog4";
|
pub static GETTEXT_PACKAGE: &str = "gtimelog4";
|
||||||
pub static LOCALEDIR: &str = "/usr/local/share/locale";
|
pub static LOCALEDIR: &str = "/usr/local/share/locale";
|
||||||
pub static PKGDATADIR: &str = "/usr/local/share/gtimelog4";
|
|
||||||
@ -3,5 +3,7 @@
|
|||||||
<gresource prefix="/de/gunibert/gtimelog4">
|
<gresource prefix="/de/gunibert/gtimelog4">
|
||||||
<file preprocess="xml-stripblanks">window.ui</file>
|
<file preprocess="xml-stripblanks">window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||||
|
<file>bigger.css</file>
|
||||||
|
<file>style.css</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
@ -26,7 +26,7 @@ mod timeentry;
|
|||||||
use self::application::Gtimelog4Application;
|
use self::application::Gtimelog4Application;
|
||||||
use self::window::Gtimelog4Window;
|
use self::window::Gtimelog4Window;
|
||||||
|
|
||||||
use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR};
|
use config::{GETTEXT_PACKAGE, LOCALEDIR};
|
||||||
use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain};
|
use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain};
|
||||||
use gtk::gio;
|
use gtk::gio;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
8
src/gui/src/style.css
Normal file
8
src/gui/src/style.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
.slackrow {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timelbl {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
146
src/gui/src/timeentry.rs
Normal file
146
src/gui/src/timeentry.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use gtk::glib;
|
||||||
|
use gtk::glib::ToValue;
|
||||||
|
use gtk::subclass::prelude::ObjectSubclassIsExt;
|
||||||
|
|
||||||
|
#[derive(glib::Boxed, Clone, Default)]
|
||||||
|
#[boxed_type(name = "Entry")]
|
||||||
|
pub struct Entry(pub data::Entry);
|
||||||
|
|
||||||
|
impl From<data::Entry> for Entry {
|
||||||
|
fn from(rentry: data::Entry) -> Self {
|
||||||
|
Self(rentry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
use crate::gio::glib::{ParamSpec, Value};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use gtk::glib::once_cell::sync::Lazy;
|
||||||
|
use gtk::subclass::prelude::{ObjectImpl, ObjectSubclass};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TimeEntry {
|
||||||
|
pub(crate) entry: RefCell<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeEntry {}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for TimeEntry {
|
||||||
|
const NAME: &'static str = "TimeEntry";
|
||||||
|
type Type = super::TimeEntry;
|
||||||
|
type ParentType = glib::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for TimeEntry {
|
||||||
|
fn properties() -> &'static [ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![
|
||||||
|
glib::ParamSpecBoxed::builder::<Entry>("entry").build(),
|
||||||
|
glib::ParamSpecString::builder("task").build(),
|
||||||
|
glib::ParamSpecString::builder("start").build(),
|
||||||
|
glib::ParamSpecString::builder("stop").build(),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"entry" => {
|
||||||
|
let e = value.get().expect("Something went wrong");
|
||||||
|
self.entry.replace(e);
|
||||||
|
}
|
||||||
|
"task" => {
|
||||||
|
let e: String = value.get().expect("Something went wrong");
|
||||||
|
println!("Set Task: {}", e);
|
||||||
|
self.entry.borrow_mut().0.task = Some(e);
|
||||||
|
}
|
||||||
|
"start" => {
|
||||||
|
let e: String = value.get().expect("Something went wront");
|
||||||
|
|
||||||
|
let start = NaiveDateTime::parse_from_str(&e, "%s").unwrap();
|
||||||
|
|
||||||
|
self.entry.borrow_mut().0.start = Some(start);
|
||||||
|
}
|
||||||
|
"stop" => {
|
||||||
|
let e: String = value.get().expect("Something went wront");
|
||||||
|
|
||||||
|
if let Ok(stop) = NaiveDateTime::parse_from_str(&e, "%s") {
|
||||||
|
self.entry.borrow_mut().0.stop = Some(stop);
|
||||||
|
} else if let Ok(stop) = chrono::NaiveTime::parse_from_str(&e, "%T") {
|
||||||
|
let mut entry = self.entry.borrow_mut();
|
||||||
|
if let Some(old_stop) = entry.0.stop {
|
||||||
|
let new_stop = NaiveDateTime::new(old_stop.date(), stop);
|
||||||
|
entry.0.stop = Some(new_stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &ParamSpec) -> Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"entry" => self.entry.borrow().to_value(),
|
||||||
|
"task" => self
|
||||||
|
.entry
|
||||||
|
.borrow()
|
||||||
|
.0
|
||||||
|
.task
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_value(),
|
||||||
|
"start" => {
|
||||||
|
let e = self.entry.borrow();
|
||||||
|
|
||||||
|
if let Some(datetime) = e.0.start {
|
||||||
|
datetime.format("%T").to_string().to_value()
|
||||||
|
} else {
|
||||||
|
"-".to_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"stop" => {
|
||||||
|
let e = self.entry.borrow();
|
||||||
|
|
||||||
|
if let Some(datetime) = e.0.stop {
|
||||||
|
datetime.format("%T").to_string().to_value()
|
||||||
|
} else {
|
||||||
|
"-".to_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct TimeEntry(ObjectSubclass<imp::TimeEntry>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeEntry {
|
||||||
|
pub fn new(entry: data::Entry) -> Self {
|
||||||
|
let e: Entry = entry.into();
|
||||||
|
glib::Object::new(&[("entry", &e.to_value())])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_task(&self) -> String {
|
||||||
|
self.imp()
|
||||||
|
.entry
|
||||||
|
.borrow()
|
||||||
|
.0
|
||||||
|
.task
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<data::Entry> for TimeEntry {
|
||||||
|
fn from(entry: data::Entry) -> Self {
|
||||||
|
TimeEntry::new(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
261
src/gui/src/window.rs
Normal file
261
src/gui/src/window.rs
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
/* window.rs
|
||||||
|
*
|
||||||
|
* Copyright 2022 Günther Wagner
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::timeentry::TimeEntry;
|
||||||
|
use adw::subclass::prelude::*;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{gio, glib, CompositeTemplate};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use gtk::glib::clone;
|
||||||
|
use gtk::{ListItem, NoSelection, SelectionModel, SignalListItemFactory};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Default, CompositeTemplate)]
|
||||||
|
#[template(resource = "/de/gunibert/gtimelog4/window.ui")]
|
||||||
|
pub struct Gtimelog4Window {
|
||||||
|
pub timelog: Rc<RefCell<data::Timelog>>,
|
||||||
|
|
||||||
|
// Template widgets
|
||||||
|
#[template_child]
|
||||||
|
pub header_bar: TemplateChild<gtk::HeaderBar>,
|
||||||
|
#[template_child]
|
||||||
|
pub columnview: TemplateChild<gtk::ColumnView>,
|
||||||
|
#[template_child]
|
||||||
|
pub start_column: TemplateChild<gtk::ColumnViewColumn>,
|
||||||
|
#[template_child]
|
||||||
|
pub stop_column: TemplateChild<gtk::ColumnViewColumn>,
|
||||||
|
#[template_child]
|
||||||
|
pub task_column: TemplateChild<gtk::ColumnViewColumn>,
|
||||||
|
#[template_child]
|
||||||
|
pub taskentry: TemplateChild<gtk::Entry>,
|
||||||
|
#[template_child]
|
||||||
|
pub statistics: TemplateChild<gtk::Label>,
|
||||||
|
|
||||||
|
pub provider: RefCell<gtk::CssProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gtimelog4Window {
|
||||||
|
fn setup_lbl(_: &SignalListItemFactory, item: &ListItem) {
|
||||||
|
let lbl = gtk::EditableLabel::new("");
|
||||||
|
lbl.set_xalign(0.0f32);
|
||||||
|
item.set_child(Some(&lbl));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_time_lbl(factory: &SignalListItemFactory, item: &ListItem) {
|
||||||
|
Gtimelog4Window::setup_lbl(factory, item);
|
||||||
|
let lbl = item.child();
|
||||||
|
if let Some(widget) = lbl {
|
||||||
|
widget.add_css_class("timelbl");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_model(&self) -> Result<gio::ListStore, anyhow::Error> {
|
||||||
|
let model: SelectionModel = self
|
||||||
|
.columnview
|
||||||
|
.model()
|
||||||
|
.ok_or(anyhow!("Columnview has not model set"))?;
|
||||||
|
let selection = model
|
||||||
|
.downcast::<NoSelection>()
|
||||||
|
.map_err(|_| anyhow!("Cannot downcast SelectionModel"))?;
|
||||||
|
let model = selection
|
||||||
|
.model()
|
||||||
|
.ok_or(anyhow!("SelectionModel has no model set"))?;
|
||||||
|
model
|
||||||
|
.downcast::<gio::ListStore>()
|
||||||
|
.map_err(|_| anyhow!("ListModel is no ListStore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_statistics(&self) {
|
||||||
|
let work = self.timelog.borrow_mut().get_work_time_today();
|
||||||
|
let work_seconds = work.num_seconds() % 60;
|
||||||
|
let work_minutes = (work.num_seconds() / 60) % 60;
|
||||||
|
let work_hours = (work.num_seconds() / 60 / 60) % 24;
|
||||||
|
|
||||||
|
let slack = self.timelog.borrow_mut().get_slack_time_today();
|
||||||
|
let slack_seconds = slack.num_seconds() % 60;
|
||||||
|
let slack_minutes = (slack.num_seconds() / 60) % 60;
|
||||||
|
let slack_hours = (slack.num_seconds() / 60 / 60) % 24;
|
||||||
|
|
||||||
|
self.statistics.set_text(&format!(
|
||||||
|
"Work: {} hours, {} minutes, {} seconds\nSlack: {} hours, {} minutes, {} seconds",
|
||||||
|
work_hours, work_minutes, work_seconds, slack_hours, slack_minutes, slack_seconds
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_factory_bind(item: &ListItem, src_prop: &str) {
|
||||||
|
if let (Some(lbl), Some(entry)) = (item.child(), item.item()) {
|
||||||
|
let mylbl = lbl.downcast::<gtk::EditableLabel>().unwrap();
|
||||||
|
let entry = entry.downcast::<TimeEntry>().unwrap();
|
||||||
|
entry
|
||||||
|
.bind_property(src_prop, &mylbl, "text")
|
||||||
|
.sync_create()
|
||||||
|
.bidirectional()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for Gtimelog4Window {
|
||||||
|
const NAME: &'static str = "Gtimelog4Window";
|
||||||
|
type Type = super::Gtimelog4Window;
|
||||||
|
type ParentType = adw::ApplicationWindow;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
Self::bind_template(klass);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for Gtimelog4Window {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.provider.replace(gtk::CssProvider::new());
|
||||||
|
self.provider
|
||||||
|
.borrow_mut()
|
||||||
|
.load_from_resource("/de/gunibert/gtimelog4/bigger.css");
|
||||||
|
|
||||||
|
self.update_statistics();
|
||||||
|
|
||||||
|
let task_factory = SignalListItemFactory::new();
|
||||||
|
task_factory.connect_setup(Gtimelog4Window::setup_lbl);
|
||||||
|
task_factory.connect_bind(clone!(@weak self as this => move |_factory, item| {
|
||||||
|
if let (Some(lbl), Some(entry)) = (item.child(), item.item()) {
|
||||||
|
let mylbl = lbl.downcast::<gtk::EditableLabel>().unwrap();
|
||||||
|
|
||||||
|
let entry = entry.downcast::<TimeEntry>().unwrap();
|
||||||
|
entry.bind_property("task", &mylbl, "text").sync_create().bidirectional().build();
|
||||||
|
|
||||||
|
if entry.get_task().starts_with("**") {
|
||||||
|
mylbl.add_css_class("slackrow");
|
||||||
|
}
|
||||||
|
|
||||||
|
mylbl.connect_changed(clone!(@weak this => move |_| {
|
||||||
|
let v: crate::timeentry::Entry = entry.property("entry");
|
||||||
|
this.timelog.borrow_mut().update_entry(&v.0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.task_column.set_factory(Some(&task_factory));
|
||||||
|
|
||||||
|
let start_factory = SignalListItemFactory::new();
|
||||||
|
start_factory.connect_setup(Gtimelog4Window::setup_time_lbl);
|
||||||
|
start_factory.connect_bind(|_factory, item| Self::time_factory_bind(item, "start"));
|
||||||
|
|
||||||
|
self.start_column.set_factory(Some(&start_factory));
|
||||||
|
|
||||||
|
let stop_factory = SignalListItemFactory::new();
|
||||||
|
stop_factory.connect_setup(Gtimelog4Window::setup_time_lbl);
|
||||||
|
stop_factory.connect_bind(|_factory, item| Self::time_factory_bind(item, "stop"));
|
||||||
|
|
||||||
|
self.stop_column.set_factory(Some(&stop_factory));
|
||||||
|
|
||||||
|
self.taskentry.connect_activate(clone!(@weak self as this, @strong self.timelog as timelog, @strong self.columnview as columnview => move |entry| {
|
||||||
|
let item = timelog.borrow_mut().add_entry(entry.text());
|
||||||
|
|
||||||
|
if let Ok(model) = this.get_model() {
|
||||||
|
let n_items = model.n_items();
|
||||||
|
if n_items > 0 {
|
||||||
|
let last_item = model.item(n_items - 1);
|
||||||
|
if let (Some(last_item), Some(start)) = (last_item, item.start) {
|
||||||
|
last_item.set_property("stop", start.format("%s").to_string().to_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeentry: TimeEntry = item.into();
|
||||||
|
|
||||||
|
model.append(&timeentry);
|
||||||
|
this.update_statistics();
|
||||||
|
entry.set_text("");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for Gtimelog4Window {
|
||||||
|
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
||||||
|
self.parent_size_allocate(width, height, baseline);
|
||||||
|
if width > 800 {
|
||||||
|
if let Some(display) = gtk::gdk::Display::default() {
|
||||||
|
gtk::StyleContext::add_provider_for_display(
|
||||||
|
&display,
|
||||||
|
self.provider.borrow().deref(),
|
||||||
|
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(display) = gtk::gdk::Display::default() {
|
||||||
|
gtk::StyleContext::remove_provider_for_display(
|
||||||
|
&display,
|
||||||
|
self.provider.borrow().deref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowImpl for Gtimelog4Window {}
|
||||||
|
|
||||||
|
impl ApplicationWindowImpl for Gtimelog4Window {}
|
||||||
|
|
||||||
|
impl AdwApplicationWindowImpl for Gtimelog4Window {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct Gtimelog4Window(ObjectSubclass<imp::Gtimelog4Window>)
|
||||||
|
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gtimelog4Window {
|
||||||
|
pub fn new<P: IsA<gtk::Application>>(application: &P) -> Self {
|
||||||
|
glib::Object::new(&[("application", application)])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_timelog(&self, timelog: data::Timelog) {
|
||||||
|
self.imp().timelog.replace(timelog);
|
||||||
|
|
||||||
|
let entries: Vec<TimeEntry> = self
|
||||||
|
.imp()
|
||||||
|
.timelog
|
||||||
|
.borrow_mut()
|
||||||
|
.get_today()
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| entry.into())
|
||||||
|
.collect();
|
||||||
|
let model = gio::ListStore::new(TimeEntry::static_type());
|
||||||
|
for entry in entries {
|
||||||
|
model.append(&entry);
|
||||||
|
}
|
||||||
|
self.imp()
|
||||||
|
.columnview
|
||||||
|
.set_model(Some(>k::NoSelection::new(Some(&model))));
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/gui/src/window.ui
Normal file
105
src/gui/src/window.ui
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<requires lib="Adw" version="1.0"/>
|
||||||
|
<template class="Gtimelog4Window" parent="AdwApplicationWindow">
|
||||||
|
<property name="default-width">600</property>
|
||||||
|
<property name="default-height">300</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHeaderBar" id="header_bar">
|
||||||
|
<child type="end">
|
||||||
|
<object class="GtkMenuButton">
|
||||||
|
<property name="icon-name">open-menu-symbolic</property>
|
||||||
|
<property name="menu-model">primary_menu</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<child>
|
||||||
|
<object class="GtkColumnView" id="columnview">
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="data-table"/>
|
||||||
|
</style>
|
||||||
|
<child>
|
||||||
|
<object class="GtkColumnViewColumn" id="start_column">
|
||||||
|
<property name="title">Start</property>
|
||||||
|
<property name="fixed-width">110</property>
|
||||||
|
<property name="resizable">true</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkColumnViewColumn" id="stop_column">
|
||||||
|
<property name="title">Stop</property>
|
||||||
|
<property name="fixed-width">110</property>
|
||||||
|
<property name="resizable">true</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkColumnViewColumn" id="task_column">
|
||||||
|
<property name="title">Task</property>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="resizable">true</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="statistics">
|
||||||
|
<property name="label">test</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="xalign">0.0</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="margin-bottom">6</property>
|
||||||
|
<property name="margin-top">6</property>
|
||||||
|
<property name="margin-start">6</property>
|
||||||
|
<property name="margin-end">6</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="taskentry">
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label">Send to Personio...</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
<menu id="primary_menu">
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">_Preferences</attribute>
|
||||||
|
<attribute name="action">app.preferences</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
|
||||||
|
<attribute name="action">win.show-help-overlay</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">_About Gtimelog4</attribute>
|
||||||
|
<attribute name="action">app.about</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
</interface>
|
||||||
@ -1,47 +0,0 @@
|
|||||||
use gtk::glib;
|
|
||||||
use gtk::subclass::prelude::ObjectSubclassExt;
|
|
||||||
use rtimelog::store::Entry;
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use super::*;
|
|
||||||
use gtk::subclass::prelude::{ObjectImpl, ObjectSubclass};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct TimeEntry {
|
|
||||||
pub(crate) entry: Rc<RefCell<rtimelog::store::Entry>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeEntry {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for TimeEntry {
|
|
||||||
const NAME: &'static str = "TimeEntry";
|
|
||||||
type Type = super::TimeEntry;
|
|
||||||
type ParentType = glib::Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for TimeEntry {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct TimeEntry(ObjectSubclass<imp::TimeEntry>);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeEntry {
|
|
||||||
pub fn new(entry: rtimelog::store::Entry) -> Self {
|
|
||||||
let obj = glib::Object::new(&[]).expect("Could not create TimeEntry");
|
|
||||||
let imp = imp::TimeEntry::from_instance(&obj);
|
|
||||||
imp.entry.replace(entry);
|
|
||||||
obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rtimelog::store::Entry> for TimeEntry {
|
|
||||||
fn from(entry: Entry) -> Self {
|
|
||||||
TimeEntry::new(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
/* window.rs
|
|
||||||
*
|
|
||||||
* Copyright 2022 Günther Wagner
|
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use adw::subclass::prelude::*;
|
|
||||||
use gtk::{gio, glib, CompositeTemplate};
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, CompositeTemplate)]
|
|
||||||
#[template(resource = "/de/gunibert/gtimelog4/window.ui")]
|
|
||||||
pub struct Gtimelog4Window {
|
|
||||||
// Template widgets
|
|
||||||
#[template_child]
|
|
||||||
pub header_bar: TemplateChild<gtk::HeaderBar>,
|
|
||||||
#[template_child]
|
|
||||||
pub columnview: TemplateChild<gtk::ColumnView>,
|
|
||||||
#[template_child]
|
|
||||||
pub task_column: TemplateChild<gtk::ColumnViewColumn>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for Gtimelog4Window {
|
|
||||||
const NAME: &'static str = "Gtimelog4Window";
|
|
||||||
type Type = super::Gtimelog4Window;
|
|
||||||
type ParentType = adw::ApplicationWindow;
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
Self::bind_template(klass);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
||||||
obj.init_template();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for Gtimelog4Window {}
|
|
||||||
impl WidgetImpl for Gtimelog4Window {}
|
|
||||||
impl WindowImpl for Gtimelog4Window {}
|
|
||||||
impl ApplicationWindowImpl for Gtimelog4Window {}
|
|
||||||
impl AdwApplicationWindowImpl for Gtimelog4Window {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct Gtimelog4Window(ObjectSubclass<imp::Gtimelog4Window>)
|
|
||||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gtimelog4Window {
|
|
||||||
pub fn new<P: glib::IsA<gtk::Application>>(application: &P) -> Self {
|
|
||||||
glib::Object::new(&[("application", application)])
|
|
||||||
.expect("Failed to create Gtimelog4Window")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk" version="4.0"/>
|
|
||||||
<requires lib="Adw" version="1.0"/>
|
|
||||||
<template class="Gtimelog4Window" parent="AdwApplicationWindow">
|
|
||||||
<property name="default-width">600</property>
|
|
||||||
<property name="default-height">300</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkHeaderBar" id="header_bar">
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="icon-name">open-menu-symbolic</property>
|
|
||||||
<property name="menu-model">primary_menu</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkColumnView" id="columnview">
|
|
||||||
<child>
|
|
||||||
<object class="GtkColumnViewColumn" id="task_column">
|
|
||||||
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</template>
|
|
||||||
<menu id="primary_menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_Preferences</attribute>
|
|
||||||
<attribute name="action">app.preferences</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
|
|
||||||
<attribute name="action">win.show-help-overlay</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_About Gtimelog4</attribute>
|
|
||||||
<attribute name="action">app.about</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user