dot/.local/share/gnome-shell/extensions/timepp@zagortenay333/sections/timer.js

1193 lines
42 KiB
JavaScript

const St = imports.gi.St;
const Gio = imports.gi.Gio
const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Clutter = imports.gi.Clutter;
const Main = imports.ui.main;
const CheckBox = imports.ui.checkBox;
const MessageTray = imports.ui.messageTray;
const Slider = imports.ui.slider;
const ByteArray = imports.byteArray;
const Signals = imports.signals;
const Mainloop = imports.mainloop;
const ME = imports.misc.extensionUtils.getCurrentExtension();
const Gettext = imports.gettext.domain(ME.metadata['gettext-domain']);
const _ = Gettext.gettext;
const ngettext = Gettext.ngettext;
const SOUND_PLAYER = ME.imports.lib.sound_player;
const FULLSCREEN = ME.imports.lib.fullscreen;
const SIG_MANAGER = ME.imports.lib.signal_manager;
const KEY_MANAGER = ME.imports.lib.keybinding_manager;
const PANEL_ITEM = ME.imports.lib.panel_item;
const NUM_PICKER = ME.imports.lib.num_picker;
const MULTIL_ENTRY = ME.imports.lib.multiline_entry;
const TEXT_LINKS_MNGR = ME.imports.lib.text_links_manager;
const MISC_UTILS = ME.imports.lib.misc_utils;
const FUZZ = ME.imports.lib.fuzzy_search;
const REG = ME.imports.lib.regex;
const IFACE = `${ME.path}/dbus/timer_iface.xml`;
const CACHE_FILE = '~/.cache/timepp_gnome_shell_extension/timepp_timer.json';
const TIMER_MAX_DURATION = 24 * 60 * 60 * 1000000; // microseconds
const TIMER_EXPIRED_MSG = _('Timer Expired!');
const TimerState = {
RUNNING : 'RUNNING',
STOPPED : 'STOPPED',
OFF : 'OFF',
};
const NotifStyle = {
STANDARD : 0,
FULLSCREEN : 1,
NONE : 2,
};
const PanelMode = {
ICON : 0,
TEXT : 1,
ICON_TEXT : 2,
DYNAMIC : 3,
};
// =====================================================================
// @@@ Main
//
// @ext : obj (main extension object)
// @settings : obj (extension settings)
// =====================================================================
var SectionMain = class SectionMain extends ME.imports.sections.section_base.SectionBase {
constructor (section_name, ext, settings) {
super(section_name, ext, settings);
this.actor.add_style_class_name('timer-section');
this.separate_menu = this.settings.get_boolean('timer-separate-menu');
this.timer_state = TimerState.OFF;
this.tic_mainloop_id = null;
this.cache_file = null;
this.cache = null;
this.notif_source = null;
this.clock = 0; // microseconds
this.end_time = 0; // for computing elapsed time (microseconds)
{
let [,xml,] = Gio.file_new_for_path(IFACE).load_contents(null);
xml = '' + ByteArray.toString(xml);
this.dbus_impl = Gio.DBusExportedObject.wrapJSObject(xml, this);
this.dbus_impl.export(Gio.DBus.session, '/timepp/zagortenay333/Timer');
}
this.linkm = new TEXT_LINKS_MNGR.TextLinksManager();
this.sigm = new SIG_MANAGER.SignalManager();
this.keym = new KEY_MANAGER.KeybindingManager(this.settings);
this.sound_player = new SOUND_PLAYER.SoundPlayer();
this.fullscreen = new TimerFullscreen(this.ext, this,
this.settings.get_int('timer-fullscreen-monitor-pos'));
this.fullscreen.set_banner_text(
this.settings.get_boolean('timer-show-seconds') ? '00:00:00' : '00:00');
try {
this.cache_file = MISC_UTILS.file_new_for_path(CACHE_FILE);
let cache_format_version =
ME.metadata['cache-file-format-version'].timer;
if (this.cache_file.query_exists(null)) {
let [a, contents, b] = this.cache_file.load_contents(null);
this.cache = JSON.parse(ByteArray.toString(contents));
}
if (!this.cache || !this.cache.format_version ||
this.cache.format_version !== cache_format_version) {
// @preset_object : { time: number (seconds),
// msg: string,
// repeat_sound: bool, }
//
// @custom_presets : array of @preset_object
// @default_preset : @preset_object
this.cache = {
format_version : cache_format_version,
default_preset : {time: 60, msg: '', repeat_sound: false},
custom_presets : [],
};
}
} catch (e) {
logError(e);
return;
}
this.current_preset = this.cache.default_preset;
//
// keybindings
//
this.keym.add('timer-keybinding-open', () => {
this.ext.open_menu(this.section_name);
});
this.keym.add('timer-keybinding-open-fullscreen', () => {
this.show_fullscreen();
});
this.keym.add('timer-keybinding-open-to-search-presets', () => {
this.ext.open_menu(this.section_name);
this._show_presets();
});
//
// panel item
//
this.panel_item.icon.gicon = MISC_UTILS.getIcon('timepp-timer-symbolic');
this.panel_item.actor.add_style_class_name('timer-panel-item');
this.panel_item.set_label(this.settings.get_boolean('timer-show-seconds') ? '00:00:00' : '00:00');
this._toggle_panel_item_mode();
//
// header
//
this.header = new St.BoxLayout({ style_class: 'timepp-menu-item header' });
this.actor.add_actor(this.header);
this.header_label = new St.Label({ x_expand: true, text: _('Timer'), style_class: 'clock' });
this.header.add_child(this.header_label);
this.icon_box = new St.BoxLayout({ y_align: Clutter.ActorAlign.CENTER, style_class: 'icon-box' });
this.header.add(this.icon_box);
this.start_pause_icon = new St.Icon({ visible: false, reactive: true, can_focus: true, track_hover: true, gicon : MISC_UTILS.getIcon('timepp-pause-symbolic'), style_class: 'pause-icon' });
this.icon_box.add_actor(this.start_pause_icon);
this.fullscreen_icon = new St.Icon({ reactive: true, can_focus: true, track_hover: true, gicon : MISC_UTILS.getIcon('timepp-fullscreen-symbolic'), style_class: 'fullscreen-icon' });
this.icon_box.add_actor(this.fullscreen_icon);
this.settings_icon = new St.Icon({ reactive: true, can_focus: true, track_hover: true, gicon : MISC_UTILS.getIcon('timepp-settings-symbolic'), style_class: 'settings-icon' });
this.icon_box.add(this.settings_icon);
//
// timer slider
//
{
this.slider_item = new St.BoxLayout({ vertical: true, style_class: 'timepp-menu-item' });
this.actor.add_child(this.slider_item);
this.slider = new Slider.Slider(0);
this.slider_item.add_actor(this.slider.actor);
}
//
// settings window container
//
this.timepicker_container = new St.Bin({ x_fill: true });
this.actor.add_child(this.timepicker_container);
//
// listen
//
this.sigm.connect(this.fullscreen, 'monitor-changed', () => {
this.settings.set_int('timer-fullscreen-monitor-pos', this.fullscreen.monitor);
});
this.sigm.connect(this.settings, 'changed::timer-separate-menu', () => {
this.separate_menu = this.settings.get_boolean('timer-separate-menu');
this.ext.update_panel_items();
});
this.sigm.connect(this.settings, 'changed::timer-show-seconds', () => this._update_time_display());
this.sigm.connect(this.settings, 'changed::timer-panel-mode', () => this._toggle_panel_item_mode());
this.sigm.connect(this.panel_item, 'middle-click', () => this.toggle_timer());
this.sigm.connect_release(this.start_pause_icon, Clutter.BUTTON_PRIMARY, true, () => this.toggle_timer());
this.sigm.connect_release(this.fullscreen_icon, Clutter.BUTTON_PRIMARY, true, () => this.show_fullscreen());
this.sigm.connect_release(this.settings_icon, Clutter.BUTTON_PRIMARY, true, () => this._show_presets());
this.sigm.connect(this.slider, 'notify::value', () => this.slider_changed(this.slider.value));
this.sigm.connect(this.slider, 'drag-end', () => this.slider_released());
this.sigm.connect(this.slider.actor, 'scroll-event', () => this.slider_released());
}
disable_section () {
this.dbus_impl.unexport();
this.stop();
this._store_cache();
this.sigm.clear();
this.keym.clear();
if (this.fullscreen) {
this.fullscreen.destroy();
this.fullscreen = null;
}
super.disable_section();
}
_store_cache () {
if (! this.cache_file.query_exists(null))
this.cache_file.create(Gio.FileCreateFlags.NONE, null);
this.cache_file.replace_contents(JSON.stringify(this.cache, null, 2),
null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
}
toggle_timer () {
if (this.timer_state === TimerState.STOPPED) this.start();
else if (this.timer_state === TimerState.RUNNING) this.stop();
}
// This func is used for DBus.
// DBus has no optional arguments, so @time === 0 and @msg === "null" means
// that those arguments are omitted, and in that case we don't update the
// default preset.
start_from_default_preset (time, msg) {
this.current_preset = this.cache.default_preset;
if (time > 0) this.current_preset.time = time;
if (msg !== "null") this.current_preset.msg = msg;
Mainloop.idle_add(() => this._store_cache());
this.start(this.current_preset.time);
}
start_from_preset (preset, time = null) {
this.current_preset = preset;
if (time !== null) {
preset.time = time;
Mainloop.idle_add(() => this._store_cache());
}
this.start(preset.time);
}
// @time: int (seconds)
start (time) {
this.timer_state = TimerState.RUNNING;
if (this.tic_mainloop_id) {
Mainloop.source_remove(this.tic_mainloop_id);
this.tic_mainloop_id = null;
}
this.sound_player.stop();
if (this.notif_source) this.notif_source.destroyNonResidentNotifications();
if (time) time *= 1000000;
else time = this.clock;
this.end_time = GLib.get_monotonic_time() + time;
this.fullscreen.on_timer_started();
this.start_pause_icon.show();
this.start_pause_icon.gicon = MISC_UTILS.getIcon('timepp-pause-symbolic');
this.start_pause_icon.style_class = 'pause-icon';
this.panel_item.actor.add_style_class_name('on');
if (this.settings.get_enum('timer-panel-mode') === PanelMode.DYNAMIC)
this.panel_item.set_mode('icon_text');
this._tic();
}
stop () {
this.timer_state = TimerState.STOPPED;
this.clock = this.end_time - GLib.get_monotonic_time();
if (this.tic_mainloop_id) {
Mainloop.source_remove(this.tic_mainloop_id);
this.tic_mainloop_id = null;
}
this.fullscreen.on_timer_stopped();
this.start_pause_icon.gicon = MISC_UTILS.getIcon('timepp-start-symbolic');
this.start_pause_icon.style_class = 'start-icon';
this.panel_item.actor.remove_style_class_name('on');
if (this.settings.get_enum('timer-panel-mode') === PanelMode.DYNAMIC)
this.panel_item.set_mode('icon');
}
reset () {
this.timer_state = TimerState.OFF;
if (this.tic_mainloop_id) {
Mainloop.source_remove(this.tic_mainloop_id);
this.tic_mainloop_id = null;
}
this.slider.value = 0;
this.fullscreen.on_timer_off();
this.header_label.text = _('Timer');
this.start_pause_icon.hide();
this.panel_item.actor.remove_style_class_name('on');
if (this.settings.get_enum('timer-panel-mode') === PanelMode.DYNAMIC)
this.panel_item.set_mode('icon');
}
_on_timer_expired () {
this.reset();
this._send_notif();
this.dbus_impl.emit_signal('timer_expired', null);
}
_tic () {
this.clock = this.end_time - GLib.get_monotonic_time();
this._update_slider();
this._update_time_display();
if (this.clock <= 0) {
this.clock = 0;
this._on_timer_expired();
return;
}
this.tic_mainloop_id = Mainloop.timeout_add_seconds(1, () => {
this._tic();
});
}
_update_time_display () {
let time = Math.ceil(this.clock / 1000000);
let txt;
// If the seconds are not shown, we need to make the timer '1-indexed'
// in respect to minutes. I.e., 00:00:34 becomes 00:01.
if (this.settings.get_boolean('timer-show-seconds')) {
txt = "%02d:%02d:%02d".format(
Math.floor(time / 3600),
Math.floor(time % 3600 / 60),
time % 60
);
} else {
if (time % 3600 !== 0) time += 60;
txt = "%02d:%02d".format(
Math.floor(time / 3600),
Math.floor(time % 3600 / 60)
);
}
this.header_label.text = txt;
this.panel_item.set_label(txt);
this.fullscreen.set_banner_text(txt);
}
// Update slider based on the clock.
// This function is the inverse of the function that is used to calc the
// clock based on the slider.
_update_slider () {
let x = this.clock / TIMER_MAX_DURATION;
let y = (Math.log(x * (Math.pow(2, 10) - 1) +1)) / Math.log(2) / 10;
this.slider.value = y;
this.fullscreen.slider.value = y;
}
slider_released () {
if (this.clock < 1000000) {
this.reset();
} else {
this.start_from_preset(this.cache.default_preset, Math.round(this.clock / 1000000));
this.start();
this._store_cache();
}
}
slider_changed (value) {
if (this.tic_mainloop_id) {
Mainloop.source_remove(this.tic_mainloop_id);
this.tic_mainloop_id = null;
}
if (value < 1) {
// Make rate of change of the timer duration an exponential curve.
// This allows for finer tuning when the duration is smaller.
let y = (Math.pow(2, (10 * value)) - 1) / (Math.pow(2, 10) - 1);
// Change the increment of the slider based on how far it's dragged.
// If the seconds are not shown, the increments must be multiples
// of 60s.
let step;
if (this.settings.get_boolean('timer-show-seconds')) {
if (value < .05) step = 15;
else if (value < .5) step = 30;
else if (value < .8) step = 60;
else step = 3600;
} else {
if (value < .7) step = 59;
else if (value < .9) step = 1800;
else step = 3600;
}
this.clock = Math.floor(y * TIMER_MAX_DURATION / step) * step;
this._update_time_display();
}
else { // slider has been dragged past the limit
this.clock = TIMER_MAX_DURATION;
this._update_time_display();
}
}
_send_notif () {
let notif_type = this.settings.get_enum('timer-notif-style');
if (notif_type === NotifStyle.NONE) {
// no visual notification
} else if (notif_type === NotifStyle.FULLSCREEN || this.fullscreen.is_open) {
this.fullscreen.open();
this.fullscreen.on_timer_expired();
} else {
if (this.notif_source) {
this.notif_source.destroyNonResidentNotifications();
}
this.notif_source = new MessageTray.Source();
Main.messageTray.add(this.notif_source);
this.notif_source.connect('destroy', () => this.sound_player.stop());
let icon = new St.Icon({ gicon : MISC_UTILS.getIcon('timepp-timer-symbolic') });
let params = {
bannerMarkup : true,
gicon : icon.gicon,
};
let notif = new MessageTray.Notification(
this.notif_source,
TIMER_EXPIRED_MSG,
this.current_preset.msg || '',
params
);
notif.setUrgency(MessageTray.Urgency.CRITICAL);
this.notif_source.notify(notif);
}
if (this.settings.get_boolean('timer-play-sound')) {
this.sound_player.set_sound_uri(this.settings.get_string('timer-sound-file-path'));
this.sound_player.play(this.current_preset.repeat_sound);
}
}
_show_presets () {
let presets_view = new TimerPresetsView(this.ext, this);
this.timepicker_container.add_actor(presets_view.actor);
Mainloop.timeout_add(0, () => presets_view.entry.entry.grab_key_focus());
this.header.hide();
this.slider_item.hide();
presets_view.connect('start-timer', (_, preset) => {
this.actor.grab_key_focus();
presets_view.actor.destroy();
this.header.show();
this.slider_item.show();
this.start_from_preset(preset);
this.ext.menu.close(false);
});
presets_view.connect('add-preset', (_, preset) => {
this.cache.custom_presets.push(preset);
this._store_cache();
});
presets_view.connect('edited-preset', (_, preset) => {
this._store_cache();
});
presets_view.connect('delete-preset', (_, preset) => {
if (this.current_preset === preset) {
this.current_preset = this.cache.default_preset;
}
for (let i = 0; i < this.cache.custom_presets.length; i++) {
if (this.cache.custom_presets[i] === preset)
this.cache.custom_presets.splice(i, 1);
}
this._store_cache();
});
presets_view.connect('ok', () => {
this.actor.grab_key_focus();
presets_view.actor.destroy();
this.header.show();
this.slider_item.show();
});
}
show_fullscreen () {
this.ext.menu.close();
if (! this.fullscreen) {
this.fullscreen = new TimerFullscreen(
this.ext, this, this.settings.get_int('timer-fullscreen-monitor-pos'));
}
this.fullscreen.open();
}
_toggle_panel_item_mode () {
switch (this.settings.get_enum('timer-panel-mode')) {
case PanelMode.ICON:
this.panel_item.set_mode('icon');
break;
case PanelMode.TEXT:
this.panel_item.set_mode('text');
break;
case PanelMode.ICON_TEXT:
this.panel_item.set_mode('icon_text');
break;
case PanelMode.DYNAMIC:
if (this.timer_state === TimerState.RUNNING) this.panel_item.set_mode('icon_text');
else this.panel_item.set_mode('icon');
}
}
highlight_tokens (text) {
text = GLib.markup_escape_text(text, -1);
text = MISC_UTILS.split_on_whitespace(text);
let inside_backticks = false;
for (let i = 0; i < text.length; i++) {
let token = text[i];
if (token.startsWith('`') || token.endsWith('`')) inside_backticks = !inside_backticks;
if (inside_backticks) continue;
if (REG.URL.test(token) || REG.FILE_PATH.test(token)) {
text[i] =
'`<span foreground="' + this.ext.custom_css['-timepp-link-color'][0] +
'"><u><b>' + token + '</b></u></span>`';
}
}
text = text.join('');
return MISC_UTILS.markdown_to_pango(text, this.ext.markdown_map);
}
}
Signals.addSignalMethods(SectionMain.prototype);
// =====================================================================
// @@@ TimerPresetsView
//
// @ext : obj (main extension object)
// @delegate : obj (main section object)
//
// @signals:
// - 'ok'
// - 'edited-preset'
// - 'add-preset' (returns a preset obj)
// - 'start-timer' (returns a preset obj)
// - 'delete-preset' (returns a preset obj)
// =====================================================================
var TimerPresetsView = class TimerPresetsView {
constructor (ext, delegate) {
this.ext = ext;
this.delegate = delegate;
this.css = this.ext.custom_css;
// objects returned by _new_preset_item() func
this.preset_items = new Set();
//
// container
//
this.actor = new St.BoxLayout({ x_expand: true, vertical: true, style_class: 'view-box' });
this.content_box = new St.BoxLayout({ x_expand: true, vertical: true, style_class: 'view-box-content' });
this.actor.add_actor(this.content_box);
//
// search presets entry
//
this.entry = new MULTIL_ENTRY.MultiLineEntry(_('Search...'), true);
this.content_box.add(this.entry.actor);
this.entry.actor.add_style_class_name('row');
this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.NEVER;
this.entry.scroll_box.hscrollbar_policy = Gtk.PolicyType.NEVER;
//
// preset items container
//
this.preset_items_scrollview = new St.ScrollView({ style_class: 'vfade' });
this.content_box.add_actor(this.preset_items_scrollview);
this.preset_items_scrollview.vscrollbar_policy = Gtk.PolicyType.NEVER;
this.preset_items_scrollview.hscrollbar_policy = Gtk.PolicyType.NEVER;
this.preset_items_scrollbox = new St.BoxLayout({ vertical: true, style_class: 'row' });
this.preset_items_scrollview.add_actor(this.preset_items_scrollbox);
{
let it = this._new_preset_item(this.delegate.cache.default_preset);
this.preset_items_scrollbox.add_child(it.actor);
it.actor.add_style_class_name('timer-preset-item-default');
it.is_default = true;
let label = new St.Label({ x_expand: true, y_align: Clutter.ActorAlign.CENTER, style_class: 'timer-preset-item-default-indicator-label' });
it.header.insert_child_at_index(label, 1);
label.clutter_text.set_markup(` <b>${_('Default preset')}</b>`);
it.header.get_first_child().x_expand = false;
}
for (let preset of this.delegate.cache.custom_presets) {
this.preset_items_scrollbox.add_child(this._new_preset_item(preset).actor);
}
//
// buttons
//
let btn_box = new St.BoxLayout({ x_expand: true, style_class: 'row btn-box' });
this.content_box.add_child(btn_box);
this.button_add_preset = new St.Button({ can_focus: true, label: _('Add Preset'), style_class: 'button', x_expand: true });
this.button_ok = new St.Button({ can_focus: true, label: _('Ok'), style_class: 'button', x_expand: true });
btn_box.add(this.button_add_preset, {expand: true});
btn_box.add(this.button_ok, {expand: true});
//
// listen
//
this.preset_items_scrollbox.connect('allocation-changed', () => {
this.preset_items_scrollview.vscrollbar_policy = Gtk.PolicyType.NEVER;
if (ext.needs_scrollbar()) this.preset_items_scrollview.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
});
this.entry.entry.connect('allocation-changed', () => {
this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.NEVER;
if (ext.needs_scrollbar()) this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
});
this.entry.entry.clutter_text.connect('text-changed', () => this._search_presets());
this.button_add_preset.connect('clicked', () => this._show_preset_editor());
this.button_ok.connect('clicked', () => this.emit('ok'));
}
_search_presets () {
this.preset_items_scrollbox.remove_all_children();
let needle = this.entry.entry.get_text().toLowerCase();
if (!needle) {
for (let it of this.preset_items)
this.preset_items_scrollbox.add_child(it.actor);
} else {
let reduced_results = [];
for (let it of this.preset_items) {
let score = FUZZ.fuzzy_search_v1(needle, it.msg.text.toLowerCase());
if (score) reduced_results.push([score, it]);
}
reduced_results.sort((a, b) => a[0] < b[0]);
for (let it of reduced_results)
this.preset_items_scrollbox.add_child(it[1].actor);
}
}
_show_preset_editor (item) {
let preset = item ? item.preset : null;
let is_deletable = Boolean(preset) && !item.is_default;
let editor = new TimerPresetEditor(this.ext, this.delegate, preset, is_deletable);
this.actor.add_child(editor.actor);
editor.entry.entry.grab_key_focus();
this.content_box.hide();
editor.connect('ok', (_, info) => {
this.content_box.show();
let it;
if (item) {
it = item;
it.preset.msg = info.msg;
it.preset.time = info.time;
it.preset.repeat_sound = info.repeat_sound;
if (info.msg) {
it.msg.clutter_text.set_markup(this.delegate.highlight_tokens(info.msg));
it.msg.show();
} else {
it.msg.hide();
}
let time_label = "%02d:%02d:%02d".format(
Math.floor(info.time / 3600),
Math.floor(info.time % 3600 / 60),
info.time % 60
);
it.time_label.clutter_text.set_markup(`<b>${time_label}</b>`);
this.emit('edited-preset');
} else {
it = this._new_preset_item(info);
this.preset_items_scrollbox.add_child(it.actor);
this.emit('add-preset', info);
}
it.icon_box.show();
it.icon_box.get_first_child().grab_key_focus();
Mainloop.idle_add(() => {
MISC_UTILS.scroll_to_item(this.preset_items_scrollview,
this.preset_items_scrollbox,
it.actor);
});
this.entry.entry.grab_key_focus();
editor.actor.destroy();
});
editor.connect('delete', () => {
this.preset_items.delete(item);
item.actor.destroy();
this.content_box.show();
this.entry.entry.grab_key_focus();
editor.actor.destroy();
this.emit('delete-preset', preset);
});
editor.connect('cancel', () => {
this.content_box.show();
this.entry.entry.grab_key_focus();
editor.actor.destroy();
});
}
_new_preset_item (preset) {
let item = {};
item.preset = preset;
this.preset_items.add(item);
item.actor = new St.BoxLayout({ can_focus: true, reactive: true, vertical: true, style_class: 'timer-preset-item' });
item.header = new St.BoxLayout();
item.actor.add_child(item.header);
item.msg = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
item.actor.add_child(item.msg);
this.delegate.linkm.add_label_actor(item.msg, new Map([
[REG.URL , MISC_UTILS.open_web_uri],
[REG.FILE_PATH , MISC_UTILS.open_file_path],
]));
if (preset.msg) {
item.msg.clutter_text.set_markup(this.delegate.highlight_tokens(preset.msg));
item.msg.show();
} else {
item.msg.hide();
}
item.time_label = new St.Label({ x_expand: true, y_align: Clutter.ActorAlign.CENTER });
item.header.add_child(item.time_label);
{
let time_label = "%02d:%02d:%02d".format(
Math.floor(preset.time / 3600),
Math.floor(preset.time % 3600 / 60),
preset.time % 60
);
item.time_label.clutter_text.set_markup(`<b>${time_label}</b>`);
}
// icons
item.icon_box = new St.BoxLayout({ visible: false, style_class: 'icon-box' });
item.header.add_child(item.icon_box);
let start_icon = new St.Icon({ track_hover: true, can_focus: true, reactive: true, gicon : MISC_UTILS.getIcon('timepp-start-symbolic') });
item.icon_box.add_child(start_icon);
let edit_icon = new St.Icon({ track_hover: true, can_focus: true, reactive: true, gicon : MISC_UTILS.getIcon('timepp-edit-symbolic') });
item.icon_box.add_child(edit_icon);
// listen
this.delegate.sigm.connect_release(start_icon, Clutter.BUTTON_PRIMARY, true, () => {
this.emit('start-timer', preset);
});
this.delegate.sigm.connect_release(edit_icon, Clutter.BUTTON_PRIMARY, true, () => {
Main.panel.menuManager.ignoreRelease();
this._show_preset_editor(item);
});
item.actor.connect('key-focus-in', () => { item.actor.can_focus = false; });
item.actor.connect('event', (_, event) => this._on_preset_item_event(item, event));
return item;
}
_on_preset_item_event (item, event) {
switch (event.type()) {
case Clutter.EventType.ENTER: {
let related = event.get_related();
if (related && !item.actor.contains(related)) item.icon_box.show();
break;
}
case Clutter.EventType.LEAVE: {
let related = event.get_related();
if (!item.header.contains(global.stage.get_key_focus()) &&
related &&
!item.actor.contains(related)) {
item.icon_box.hide();
item.actor.can_focus = true;
}
break;
}
case Clutter.EventType.KEY_RELEASE: {
item.icon_box.show();
if (!item.header.contains(global.stage.get_key_focus())) {
item.icon_box.get_first_child().grab_key_focus();
}
MISC_UTILS.scroll_to_item(this.preset_items_scrollview,
this.preset_items_scrollbox,
item.actor);
break;
}
case Clutter.EventType.KEY_PRESS: {
Mainloop.idle_add(() => {
if (item.icon_box && !item.header.contains(global.stage.get_key_focus())) {
item.actor.can_focus = true;
item.icon_box.hide();
}
});
break;
}
}
}
}
Signals.addSignalMethods(TimerPresetsView.prototype);
// =====================================================================
// @@@ TimerPresetEditor
//
// @ext : obj (main extension object)
// @delegate : obj (main section object)
// @preset : obj
//
// @signals: 'ok', 'cancel', 'delete'
// =====================================================================
var TimerPresetEditor = class TimerPresetEditor {
constructor (ext, delegate, preset, is_deletable) {
this.ext = ext;
this.delegate = delegate;
this.preset = preset;
//
// container
//
this.actor = new St.BoxLayout({ vertical: true, style_class: 'view-box-content' });
//
// time pickers
//
{
let box = new St.BoxLayout({ style_class: 'row numpicker-box' });
this.actor.add_actor(box);
let label = new St.Label({ x_expand: true, y_align: Clutter.ActorAlign.CENTER });
box.add_child(label);
this.hr = new NUM_PICKER.NumPicker(0, 23);
box.add_child(this.hr.actor);
this.min = new NUM_PICKER.NumPicker(0, 59);
box.add_child(this.min.actor);
if (this.delegate.settings.get_boolean('timer-show-seconds')) {
label.text = `${_('(h:min:sec)')} `;
this.sec = new NUM_PICKER.NumPicker(0, 59);
box.add_child(this.sec.actor);
} else {
label.text = `${_('(h:min)')} `;
}
}
this._set_time();
//
// entry
//
this.entry_container = new St.BoxLayout({ x_expand: true, style_class: 'row entry-container' });
this.actor.add_actor(this.entry_container);
this.entry = new MULTIL_ENTRY.MultiLineEntry(_('Timer message...'), true);
this.entry_container.add(this.entry.actor, {expand: true});
this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.NEVER;
this.entry.scroll_box.hscrollbar_policy = Gtk.PolicyType.NEVER;
if (preset) this.entry.set_text(preset.msg);
//
// repeat sound checkbox
//
this.checkbox_item = new St.BoxLayout({ reactive: true, x_expand: true, style_class: 'row' });
this.actor.add_actor(this.checkbox_item);
this.checkbox_item.add_child(
new St.Label({ text: _('Repeat notification sound?'), x_expand: true, y_align: Clutter.ActorAlign.CENTER }));
this.sound_checkbox = new CheckBox.CheckBox();
this.checkbox_item.add_child(this.sound_checkbox.actor);
this.sound_checkbox.actor.checked = preset ? preset.repeat_sound : false;
//
// buttons
//
let btn_box = new St.BoxLayout({ style_class: 'row btn-box' });
this.actor.add(btn_box, {expand: true});
if (is_deletable) {
this.button_delete = new St.Button({ can_focus: true, label: _('Delete'), style_class: 'btn-delete button', x_expand: true });
btn_box.add(this.button_delete, {expand: true});
this.button_delete.connect('clicked', () => this.emit('delete'));
}
this.button_cancel = new St.Button({ can_focus: true, label: _('Cancel'), style_class: 'btn-cancel button', x_expand: true });
this.button_ok = new St.Button({ can_focus: true, label: _('Ok'), style_class: 'btn-ok button', x_expand: true });
btn_box.add(this.button_cancel, {expand: true});
btn_box.add(this.button_ok, {expand: true});
//
// listen
//
this.entry.entry.connect('allocation-changed', () => {
this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.NEVER;
if (ext.needs_scrollbar()) this.entry.scroll_box.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
});
this.button_ok.connect('clicked', () => {
this.emit('ok', {
time : this._get_time(),
msg : this.entry.entry.get_text(),
repeat_sound : this.sound_checkbox.actor.checked,
});
});
this.button_cancel.connect('clicked', () => this.emit('cancel'));
this.checkbox_item.connect('button-press-event', () => {
this.sound_checkbox.actor.checked = !this.sound_checkbox.actor.checked;
});
}
_set_time () {
if (! this.preset) return;
this.hr.set_counter(Math.floor(this.preset.time / 3600));
this.min.set_counter(Math.floor(this.preset.time % 3600 / 60));
if (this.sec) this.sec.set_counter(this.preset.time % 60);
}
_get_time () {
let h = this.hr.counter * 3600;
let min = this.min.counter * 60;
let sec = this.sec ? this.sec.counter : 0;
return h + min + sec;
}
}
Signals.addSignalMethods(TimerPresetEditor.prototype);
// =====================================================================
// @@@ Timer fullscreen interface
//
// @ext : obj (main extension object)
// @delegate : obj (main section object)
// @monitor : int
//
// @signals: 'monitor-changed'
// =====================================================================
var TimerFullscreen = class TimerFullscreen extends FULLSCREEN.Fullscreen {
constructor (ext, delegate, monitor) {
super(monitor);
this.default_style_class = this.actor.style_class;
this.ext = ext;
this.delegate = delegate;
this.delegate.linkm.add_label_actor(this.banner, new Map([
[REG.URL , MISC_UTILS.open_web_uri],
[REG.FILE_PATH , MISC_UTILS.open_file_path],
]));
//
// actors
//
this.title = new St.Label({ x_expand: true, x_align: Clutter.ActorAlign.CENTER, style_class: 'pomo-phase-label' });
this.middle_box.insert_child_at_index(this.title, 0);
this.slider = new Slider.Slider(0);
this.bottom_box.add_child(this.slider.actor);
this.slider.actor.can_focus = true;
this.start_pause_btn = new St.Button();
this.top_box.insert_child_at_index(this.start_pause_btn, 0);
this.start_pause_icon = new St.Icon({ visible: false, reactive: true, can_focus: true, track_hover: true, gicon : MISC_UTILS.getIcon('timepp-pause-symbolic'), style_class: 'pause-icon' });
this.start_pause_btn.add_actor(this.start_pause_icon);
//
// listen
//
this.start_pause_btn.connect('clicked', () => {
this.delegate.toggle_timer();
});
this.slider.connect('drag-end', () => {
this.delegate.slider_released();
});
this.slider.actor.connect('scroll-event', () => {
this.delegate.slider_released();
this.title.text = '';
});
this.slider.connect('notify::value', () => {
this.delegate.slider_changed(this.slider.value);
this.actor.remove_style_class_name('timer-expired');
this.title.text = '';
});
this.actor.connect('key-release-event', (_, event) => {
switch (event.get_key_symbol()) {
case Clutter.KEY_space:
if (this.delegate.timer_state !== TimerState.OFF)
this.delegate.toggle_timer();
return Clutter.EVENT_STOP;
case Clutter.KEY_r:
case Clutter.KEY_BackSpace:
this.delegate.start(this.delegate.current_preset.time);
return Clutter.EVENT_STOP;
case Clutter.KEY_1:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 60);
this.delegate.start(60);
return Clutter.EVENT_STOP;
case Clutter.KEY_2:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 2 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_3:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 3 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_4:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 4 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_5:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 5 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_6:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 6 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_7:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 7 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_8:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 8 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_9:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 9 * 60);
return Clutter.EVENT_STOP;
case Clutter.KEY_0:
this.delegate.start_from_preset(this.delegate.cache.default_preset, 10 * 60);
return Clutter.EVENT_STOP;
default:
return Clutter.EVENT_PROPAGATE;
}
});
}
close () {
this.delegate.sound_player.stop();
if (this.delegate.timer_state === TimerState.OFF) {
this.actor.style_class = this.default_style_class;
this.title.text = '';
this.set_banner_text(
this.delegate.settings.get_boolean('timer-show-seconds') ? '00:00:00' : '00:00');
}
super.close();
}
on_timer_started () {
this.actor.style_class = this.default_style_class;
this.title.text = '';
this.start_pause_icon.gicon = MISC_UTILS.getIcon('timepp-pause-symbolic');
this.start_pause_icon.style_class = 'pause-icon';
this.start_pause_icon.show();
}
on_timer_stopped () {
this.actor.style_class = this.default_style_class + ' timer-stopped';
this.start_pause_icon.gicon = MISC_UTILS.getIcon('timepp-start-symbolic');
this.start_pause_icon.style_class = 'start-icon';
}
on_timer_off () {
this.start_pause_icon.hide();
this.slider.value = 0;
}
on_timer_expired () {
if (this.delegate.current_preset.msg) {
this.title.text = TIMER_EXPIRED_MSG;
this.set_banner_text(this.delegate.highlight_tokens(this.delegate.current_preset.msg));
} else {
this.set_banner_text(TIMER_EXPIRED_MSG);
}
this.actor.style_class = this.default_style_class + ' timer-expired';
}
}
Signals.addSignalMethods(TimerFullscreen.prototype);