use gdk::{RGBA, SELECTION_PRIMARY};
use gtk;
use gtk::{
BoxExt,
Clipboard,
CssProvider,
CssProviderExt,
EditableExt,
EditableSignals,
EntryExt,
LabelExt,
OrientableExt,
PackType,
StateFlags,
StyleContextExt,
WidgetExt,
STYLE_PROVIDER_PRIORITY_APPLICATION,
};
use gtk::Orientation::Horizontal;
use pango::EllipsizeMode;
use relm::{Relm, Widget};
use relm_attributes::widget;
use self::Msg::*;
use self::ItemMsg::{Color, Text};
#[derive(Msg)]
pub enum Msg {
BarVisible(bool),
Copy,
Cut,
DeleteNextChar,
DeleteNextWord,
DeletePreviousWord,
End,
EntryActivate(Option<String>),
EntryChanged(Option<String>),
EntryText(String),
EntryShown(bool),
Identifier(String),
NextChar,
NextWord,
Paste,
PasteSelection,
PreviousChar,
PreviousWord,
ShowIdentifier,
SmartHome,
}
pub struct Model {
identifier_label: &'static str,
identifier_visible: bool,
relm: Relm<StatusBar>,
visible: bool,
}
#[widget]
impl Widget for StatusBar {
fn init_view(&mut self) {
let style_context = self.command_entry.get_style_context();
let style = include_bytes!("../../style/command-input.css");
let provider = CssProvider::new();
provider.load_from_data(style).unwrap();
style_context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_APPLICATION);
}
fn model(relm: &Relm<Self>, _: ()) -> Model {
Model {
identifier_label: ":",
identifier_visible: false,
relm: relm.clone(),
visible: true,
}
}
fn set_entry_shown(&mut self, visible: bool) {
let _lock = self.model.relm.stream().lock();
self.command_entry.set_text("");
self.model.identifier_visible = visible;
self.command_entry.set_visible(visible);
if visible {
self.command_entry.grab_focus();
}
}
fn show_identifier(&mut self) {
self.model.identifier_visible = true;
}
fn update(&mut self, msg: Msg) {
match msg {
BarVisible(visible) => self.model.visible = visible,
Copy => self.copy(),
Cut => self.cut(),
DeleteNextChar => self.delete_next_char(),
DeleteNextWord => self.delete_next_word(),
DeletePreviousWord => self.delete_previous_word(),
End => self.end(),
EntryActivate(_) | EntryChanged(_) => (),
EntryShown(visible) => self.set_entry_shown(visible),
EntryText(input) => self.set_input(&input),
Identifier(identifier) => self.set_identifier(&identifier),
NextChar => self.next_char(),
NextWord => self.next_word(),
Paste => self.paste(),
PasteSelection => self.paste_selection(),
PreviousChar => self.previous_char(),
PreviousWord => self.previous_word(),
ShowIdentifier => self.show_identifier(),
SmartHome => self.smart_home(),
}
}
view! {
#[container]
gtk::Box {
property_height_request: 20,
orientation: Horizontal,
visible: self.model.visible,
#[name="identifier_label"]
gtk::Label {
text: self.model.identifier_label,
visible: self.model.identifier_visible,
},
#[name="command_entry"]
gtk::Entry {
activate(entry) => EntryActivate(entry.get_text().map(|text| text.to_string())),
changed(entry) => EntryChanged(entry.get_text().map(|text| text.to_string())),
has_frame: false,
hexpand: true,
name: "mg-input-command",
},
}
}
}
impl StatusBar {
fn copy(&self) {
self.command_entry.copy_clipboard();
}
fn cut(&self) {
self.command_entry.cut_clipboard();
}
fn delete_next_char(&self) {
if !self.delete_selection() {
if let Some(text) = self.get_command() {
if !text.is_empty() {
let pos = self.command_entry.get_position();
let len = text.chars().count();
if pos < len as i32 {
let _lock = self.model.relm.stream().lock();
self.command_entry.delete_text(pos, pos + 1);
}
}
}
}
self.emit_entry_changed();
}
fn delete_next_word(&self) {
if !self.delete_selection() {
if let Some(text) = self.get_command() {
if !text.is_empty() {
let pos = self.command_entry.get_position();
let end = text.chars().enumerate()
.skip(pos as usize)
.skip_while(|&(_, c)| !c.is_alphanumeric())
.skip_while(|&(_, c)| c.is_alphanumeric())
.map(|(index, _)| index)
.next()
.unwrap_or_else(|| text.chars().count());
let _lock = self.model.relm.stream().lock();
self.command_entry.delete_text(pos, end as i32);
}
}
}
self.emit_entry_changed();
}
fn delete_previous_word(&self) {
if !self.delete_selection() {
if let Some(text) = self.get_command() {
if !text.is_empty() {
let pos = self.command_entry.get_position();
let len = text.chars().count();
let start = text.chars().rev().enumerate()
.skip(len - pos as usize)
.skip_while(|&(_, c)| !c.is_alphanumeric())
.skip_while(|&(_, c)| c.is_alphanumeric())
.map(|(index, _)| len - index)
.next()
.unwrap_or_default();
let _lock = self.model.relm.stream().lock();
self.command_entry.delete_text(start as i32, pos);
}
}
}
self.emit_entry_changed();
}
fn delete_selection(&self) -> bool {
if self.command_entry.get_selection_bounds().is_some() {
let _lock = self.model.relm.stream().lock();
self.command_entry.delete_selection();
true
}
else {
false
}
}
fn emit_entry_changed(&self) {
self.model.relm.stream().emit(EntryChanged(self.command_entry.get_text().map(|text| text.to_string())));
}
fn end(&self) {
let text = self.get_command().unwrap_or_default();
self.command_entry.set_position(text.chars().count() as i32);
}
fn get_command(&self) -> Option<String> {
self.command_entry.get_text().map(|text| text.to_string())
}
fn next_char(&self) {
let pos = self.command_entry.get_position();
let text = self.get_command().unwrap_or_default();
if pos < text.chars().count() as i32 {
self.command_entry.set_position(pos + 1);
}
}
fn next_word(&self) {
let pos = self.command_entry.get_position();
let text = self.get_command().unwrap_or_default();
let position = text.chars().enumerate()
.skip(pos as usize)
.skip_while(|&(_, c)| !c.is_alphanumeric())
.skip_while(|&(_, c)| c.is_alphanumeric())
.next()
.map(|(index, _)| index)
.unwrap_or_else(|| text.chars().count());
self.command_entry.set_position(position as i32);
}
fn paste(&self) {
self.command_entry.paste_clipboard();
}
fn paste_selection(&self) {
self.delete_selection();
let clipboard = Clipboard::get(&SELECTION_PRIMARY);
if let Some(text) = clipboard.wait_for_text() {
let mut position = self.command_entry.get_position();
self.command_entry.insert_text(&text, &mut position);
self.command_entry.set_position(position);
}
}
fn previous_char(&self) {
let pos = self.command_entry.get_position();
if pos > 0 {
self.command_entry.set_position(pos - 1);
}
}
fn previous_word(&self) {
let pos = self.command_entry.get_position();
let text = self.get_command().unwrap_or_default();
let len = text.chars().count();
let position = text.chars().rev().enumerate()
.skip(len - pos as usize)
.skip_while(|&(_, c)| !c.is_alphanumeric())
.skip_while(|&(_, c)| c.is_alphanumeric())
.next()
.map(|(index, _)| len - index)
.unwrap_or_default();
self.command_entry.set_position(position as i32);
}
fn set_identifier(&self, identifier: &str) {
self.identifier_label.set_text(identifier);
}
fn set_input(&self, command: &str) {
let _lock = self.model.relm.stream().lock();
self.command_entry.set_text(command);
self.command_entry.set_position(command.chars().count() as i32);
}
fn smart_home(&self) {
let pos = self.command_entry.get_position();
if pos == 0 {
let text = self.get_command().unwrap_or_default();
let position = text.chars().enumerate()
.skip_while(|&(_, c)| c.is_whitespace())
.skip_while(|&(_, c)| !c.is_whitespace())
.skip_while(|&(_, c)| c.is_whitespace())
.map(|(index, _)| index)
.next()
.unwrap_or_default();
self.command_entry.set_position(position as i32);
}
else {
self.command_entry.set_position(0);
}
}
}
#[derive(Msg)]
pub enum ItemMsg {
Color(Option<RGBA>),
Text(String),
}
#[widget]
impl Widget for StatusBarItem {
fn model() -> () {
()
}
fn update(&mut self, msg: ItemMsg) {
match msg {
Color(color) => self.label.override_color(StateFlags::NORMAL, color.as_ref()),
Text(text) => self.label.set_text(&text),
}
}
view! {
#[parent="status-bar-item"]
#[name="label"]
gtk::Label {
ellipsize: EllipsizeMode::End,
child: {
pack_type: PackType::End,
padding: 3,
},
}
}
}