newt 0.7.2

Rust bindings for the Newt console UI library
Documentation
//
// Copyright (C) 2019,2020 Robert Gill <rtgill82@gmail.com>
//
// This file is a part of newt-rs.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License version 2.1 as published by the Free Software Foundation.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::cell::Cell;
use std::ops::Drop;
use std::os::unix::io::RawFd;
use std::ptr;

use newt_sys::*;
use crate::callbacks::HelpCallback;
use crate::component::Component;
use crate::error::{Error,Result};
use crate::widgets::VerticalScrollbar;
use crate::private::{Child,Nullify};
use crate::private::funcs::*;

mod constants;
use self::constants::*;

mod exit_reason;
pub use self::exit_reason::ExitReason;

///
/// Displays `Component`s and accepts user input.
///
#[derive(Component)]
pub struct Form<'a>
{
    co: Cell<newtComponent>,
    added_to_parent: Cell<bool>,
    components: Vec<&'a dyn Component>
}

#[derive(Component)]
struct BaseComponent {
    co: Cell<newtComponent>,
    added_to_parent: Cell<bool>
}

///
/// File descriptor flags for the [`Form.watch_fd()`][watch_fd] function.
///
/// [watch_fd]: crate::widgets::form::Form::watch_fd
///
#[repr(C)]
pub enum FDFlags {
    /// Exit when the file descriptor is ready for reading.
    Read   = NEWT_FD_READ as isize,
    /// Exit when the file descriptor is ready for writing.
    Write  = NEWT_FD_WRITE as isize,
    /// Exit when an exception has occurred on the file descriptor.
    Except = NEWT_FD_EXCEPT as isize
}

impl<'a> Form<'a>
{
    ///
    /// Creates a new `Form`.
    ///
    pub fn new(scrollbar: Option<&VerticalScrollbar>, flags: i32) -> Form<'a> {
        let scrollbar = if let Some(scrollbar) = scrollbar {
            scrollbar.co()
        } else {
            ptr::null_mut()
        };

        let co = unsafe { newtForm(scrollbar, ptr::null_mut(), flags) };
        if co.is_null() {
            malloc_failure();
        }

        Form {
            co: Cell::new(co),
            added_to_parent: Cell::new(false),
            components: Vec::new()
        }
    }

    ///
    /// Creates a new `Form` with an associated help `HelpCallback`. See
    /// [`HelpCallback`] for additional information.
    ///
    pub fn new_with_help_callback<F, T>(scrollbar: Option<&VerticalScrollbar>,
                                        flags: i32,
                                        function: F,
                                        data: Option<T>)
        -> (Form<'a>, Box<HelpCallback<'a, F, T>>)
    where
        F: Fn(&Form, Option<&T>)
    {
        HelpCallback::new(scrollbar, flags, data, function)
    }

    pub(crate) fn new_co(co: newtComponent) -> Form<'a> {
        Form {
            co: Cell::new(co),
            components: Vec::new(),
            added_to_parent: Cell::new(false)
        }
    }

    #[cfg(feature = "asm")]
    pub(crate) fn add_refs(&mut self, components: Vec<&'a dyn Component>) {
        let len = components.len();
        self.components.reserve(len);
        for co in components.iter() {
            self.components.push(*co);
        }
    }

    ///
    /// Add a `Component` to the `Form` to be displayed when the `Form` is run.
    ///
    pub fn add_component(&mut self, component: &'a dyn Component)
        -> Result<()>
    {
        component.add_to_parent()?;
        self.components.push(component);
        unsafe { newtFormAddComponent(self.co(), component.co()); }
        Ok(())
    }

    ///
    /// Add multiple `Component`s to the `Form`.
    ///
    pub fn add_components<'t>(&mut self, components: &'t [&'a dyn Component])
        -> Result<()>
    {
        let len = components.len();
        self.components.reserve(len);
        for component in components.iter() {
            self.add_component(*component)?;
        }
        Ok(())
    }

    ///
    /// Add a `Component` to the `Form`, taking ownership.
    ///
    pub fn take_component<T>(&mut self, component: T)
        -> Result<()>
    where
        T: Component
    {
        component.add_to_parent()?;
        unsafe { newtFormAddComponent(self.co(), component.co()); }
        Ok(())
    }

    ///
    /// Set the height of the `Form`.
    ///
    pub fn set_height(&self, height: i32) {
        unsafe { newtFormSetHeight(self.co(), height); }
    }

    ///
    /// Set the width of the `Form`.
    ///
    pub fn set_width(&self, width: i32) {
        unsafe { newtFormSetWidth(self.co(), width); }
    }

    ///
    /// Tell the `Form` to resize itself.
    ///
    pub fn set_size(&self) {
        unsafe { newtFormSetSize(self.co()); }
    }

    ///
    /// Add an exit hot key to the `Form`. The `Form` will stop running
    /// when the key is pressed. Defaults to `F12`. Use the
    /// [FORM_NOF12][form_nof12] flag to disable the default.
    ///
    /// [form_nof12]: crate::constants::form::FORM_NOF12
    ///
    pub fn add_hot_key(&self, key: i32) {
        unsafe { newtFormAddHotKey(self.co(), key); }
    }

    ///
    /// Add an exit timer to the `Form`. The `Form` will stop running
    /// when the timer times out.
    ///
    /// * `millisecs` - The timer countdown in milliseconds.
    ///
    pub fn set_timer(&self, millisecs: i32) {
        unsafe { newtFormSetTimer(self.co(), millisecs); }
    }

    ///
    /// Add a file descriptor for the `Form` to watch. The `Form` will stop
    /// running when the specified activity occurs on the file descriptor.
    ///
    /// * `fd` - The file descriptor to watch.
    /// * `flags` - Flags specifying the activity to watch for.
    ///
    pub fn watch_fd(&self, fd: RawFd, flags: FDFlags) {
        unsafe { newtFormWatchFd(self.co(), fd, flags as i32); }
    }

    ///
    /// Get the `Form`'s currently focused `Component`.
    ///
    pub fn get_current(&self) -> Box<dyn Component> {
        Box::new(BaseComponent {
            co: unsafe { Cell::new(newtFormGetCurrent(self.co())) },
            added_to_parent: Cell::new(true)
        })
    }

    ///
    /// Set the `Form`'s currently focused `Component`.
    ///
    pub fn set_current(&self, subcomponent: &dyn Component) {
        unsafe { newtFormSetCurrent(self.co(), subcomponent.co()); }
    }

    ///
    /// Set the `Form`'s background color.
    ///
    pub fn set_background(&self, color: i32) {
        unsafe { newtFormSetBackground(self.co(), color); }
    }

    ///
    /// Get the current vertical scroll position of the `Form`.
    ///
    pub fn get_scroll_position(&self) -> i32 {
        unsafe { newtFormGetScrollPosition(self.co()) }
    }

    ///
    /// Set the vertical scroll position of the `Form`.
    ///
    pub fn set_scroll_position(&self, position: i32) {
        unsafe { newtFormSetScrollPosition(self.co(), position); }
    }

    ///
    /// Run the form displaying all added components and accept input from
    /// the user.
    ///
    pub fn run(&self) -> Result<ExitReason> {
        use self::ExitReason::{HotKey,Component,FDReady,Timer};

        let mut es = newtExitStruct {
            reason: NEWT_EXIT_HOTKEY,
            u: newtExitStructUnion { watch: 0 }
        };

        unsafe {
            newtFormRun(self.co(), &mut es);
            match es.reason {
                NEWT_EXIT_HOTKEY => Ok(HotKey(es.u.key)),
                NEWT_EXIT_COMPONENT => Ok(
                    Component(Box::new(BaseComponent {
                                       co: Cell::new(es.u.co),
                                       added_to_parent: Cell::new(true)
                    }))
                ),
                NEWT_EXIT_FDREADY => Ok(FDReady(es.u.watch)),
                NEWT_EXIT_TIMER => Ok(Timer),
                NEWT_EXIT_ERROR => Err(Error::FormRun),
                _ => panic!("Unexpected newt exit reason.")
            }
        }
    }

    ///
    /// Redraw the `Form`.
    ///
    pub fn draw(&self) {
        unsafe { newtDrawForm(self.co()); }
    }
}

impl<'a> Drop for Form<'a>
{
    fn drop(&mut self) {
        if !self.added_to_parent() {
            unsafe { newtFormDestroy(self.co()); }
            self.nullify();
        }

        for component in self.components.iter() {
            component.nullify();
        }
    }
}