question_bank_creator/
lib.rs

1
2/*
3        // TODO: Font Libre Baskerville, 14 pt.  -- next iteration.
4        // TODO: All this needs to be user modifiable. -- next iteration.
5        // TODO: Goal is for  WYSIWIG.  --  in the far, far future.
6
7        // TODO: Add second line to the title containing the associated textbook text.
8        // TODO: Question display should show calculated values for the variables
9        //          rather than the variable ID.  Maybe highlight the values so
10        //          that the variable can be easily located.  May also want to set up 
11        //          a hover over the value/variable to show the variable's value/variable name.
12        
13        // TODO: Refine the implementation of how you use LastDirUsed.  It is currently
14        //          inconsistent in how it is applied.  Check every instance where a
15        //          file is read or written -- primarily in the *_read() and *_save()
16        //          functions.  Also, check the three utility crates.  The 
17        //          file_browse_save_fltr() function will need attention.   
18
19 */  // TODO's
20
21use fltk::window;
22use fltk::window::Window;
23use crate::banks::Bank;
24use fltk::app::App;
25use fltk::group::Scroll;
26use fltk::prelude::{GroupExt, WidgetBase, WidgetExt};
27use fltk::text::{TextDisplay, TextEditor};
28use fltk::utils::oncelock::Lazy;
29use std::sync::Mutex;
30
31// region  Global Constants
32
33/// The current iteration of the program being worked on.
34/// 
35pub const DEVELOPMENT_VERSION: &str = "Question Bank Rebuild 4";
36/// The title of the project.
37/// 
38pub const PROGRAM_TITLE: &str = "Question Bank Creator";
39/// The current version..
40/// 
41pub const VERSION: &str = "0.29.8";  // Note:  Versioning, while semantic in format, is decimal in nature.
42
43
44// Note:  The constants below are here during development.  They will be moved to
45//          a config file once the program is more stable.
46
47/// The default folder where data is saved.
48/// 
49pub const DATA_GENERAL_FOLDER: &str = "/home/jtreagan/programming/mine/qbnk_rb7/src/qbnk_data";
50/// The default folder for saving Lists.
51/// 
52pub const LIST_DIR: &str = "/home/jtreagan/programming/mine/qbnk_rb7/src/qbnk_data/lists";
53/// The default folder for saving Variables.
54/// 
55pub const VARIABLE_DIR: &str = "/home/jtreagan/programming/mine/qbnk_rb7/src/qbnk_data/variables";
56/// The default folder for saving Banks.
57/// 
58pub const BANK_DIR: &str = "/home/jtreagan/programming/mine/qbnk_rb7/src/qbnk_data/banks";
59
60/// Default height of the question display.
61/// 
62pub const QDISP_HEIGHT: i32 = 150;
63/// Default width of the scrollbar group.
64/// 
65pub const SCROLLBAR_WIDTH: i32 = 15;
66// endregion
67
68//region Global Variables
69/// Contains the question Bank that is currently being edited.
70/// 
71pub static CURRENT_BANK: Lazy<Mutex<Bank>> = Lazy::new(|| Mutex::new(Bank::new()));
72/// Contains the last directory path that was used.
73/// 
74pub static LAST_DIR_USED: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
75/// Holds the currently running FLTK App.
76/// 
77pub static APP_FLTK: Lazy<Mutex<App>> = Lazy::new(|| Mutex::new(App::default()));
78/// Holds the FLTK widgets currently being used.
79/// 
80pub static WIDGETS: Lazy<Mutex<Wdgts>> = Lazy::new(|| Mutex::new(Wdgts::new()));
81//endregion
82
83// region Global Structs & Enums
84/// Struct that holds the primary window's widgets.
85///
86pub struct Wdgts {
87    pub prim_win: Window,
88    pub title_editbox: TextEditor,
89    pub scroll: Scroll,
90    pub qstn_boxes: Vec<TextDisplay>,
91}
92
93impl Wdgts {
94    pub fn new() -> Self {
95        let prim_win = window::Window::new(1100, 200, 825, 900, PROGRAM_TITLE)
96            .with_size(825, 900)
97            .with_pos(1100, 200);
98        prim_win.end();
99
100        Self {
101            prim_win,
102            title_editbox: TextEditor::default(),
103            scroll: Scroll::default(),
104            qstn_boxes: Vec::new(),
105        }
106    }
107}
108
109impl Clone for Wdgts {
110    fn clone(&self) -> Self {
111        Self {
112            prim_win: self.prim_win.clone(),
113            title_editbox: self.title_editbox.clone(),
114            scroll: self.scroll.clone(),
115            qstn_boxes: self.qstn_boxes.clone(),
116        }
117    }
118}
119
120// endregioncar
121
122/// Holds the TypeWrapper enum.
123///
124pub mod global {
125    // todo:  Do you really need this?  I don't think you can
126    //          eliminate it, but maybe you can move it to the
127    //          global structs section and eliminate the global module.
128
129    use serde::{Deserialize, Serialize};
130
131    #[derive(Debug, Serialize, Deserialize, Clone)]
132    pub enum TypeWrapper {
133        Alphanum(String),
134        Letter(char),
135        Integer(i64),
136        Floating(f64),
137    }
138}  // End   global   module
139
140/// Functions that deal with the Bank struct.
141///
142pub mod banks {
143    use crate::misc::{make_question_boxes, make_scrollgroup, make_title_txtedtr};
144    use crate::{questions::*, Wdgts, APP_FLTK, BANK_DIR, CURRENT_BANK, LAST_DIR_USED, WIDGETS};
145    use fltk::prelude::{DisplayExt, GroupExt, WidgetExt};
146    use fltk::text::TextBuffer;
147    use lib_file::{file_fltk::*, file_mngmnt::file_read_to_string};
148    use lib_myfltk::{input_fltk::*};
149    use serde::{Deserialize, Serialize};
150    use std::{fs::File, io::Write};
151
152    //region Struct Section
153
154    /// The outermost of the three structs QBC is built around.
155    /// Note that the field `question_vec: Vec<Question>` is a vector
156    /// of type `Question`.  `Question` is the second layer in the
157    /// three-struct nest.
158    #[derive(Debug, Serialize, Deserialize)]
159    pub struct Bank {
160        pub bank_title: String,   // Also used for file name.
161        pub associated_textbook: String,   // Use  ""  if no text being used.
162        pub question_vec: Vec<Question>,
163    }
164
165    impl Bank {
166
167        /// Initialize a new question Bank.
168        pub fn new() -> Bank {
169            Self {
170                bank_title: "No Bank Loaded".to_string(),
171                associated_textbook: "Untitled Textbook".to_string(),
172                question_vec: Vec::new(),
173            }
174        }
175    }
176
177    impl Clone for Bank {
178
179        /// Clone a question Bank.
180        fn clone(&self) -> Self {
181            Self {
182                bank_title: self.bank_title.clone(),
183                associated_textbook: self.associated_textbook.clone(),
184                question_vec: self.question_vec.clone(), // Vec itself does implement Clone.
185            }
186        }
187    }
188
189    //endregion
190
191    /// Refreshes the contents of the widgets that are currently being displayed
192    /// and edited in the primary window.
193    pub fn bnk_refresh_widgets() {
194        let mut wdgts: Wdgts;
195        {
196            wdgts = WIDGETS.lock().unwrap().clone();
197        }
198
199        wdgts.scroll.clear();
200        wdgts.scroll.redraw();
201
202          // Create/refresh widgets based on data in CURRENT_BANK.
203        make_title_txtedtr();
204        make_scrollgroup();
205        make_question_boxes();
206    }
207
208    /// Creates a brand new question bank.
209    ///
210    pub fn bnk_create() {
211        
212        // todo: Check for bank in memory before proceeding.
213        
214        let app;
215        {
216            app = APP_FLTK.lock().unwrap().clone();
217        } // Access the main app.
218
219        // Input values into the struct fields.
220        let mut newbank = Bank::new();
221        newbank.bank_title = input_string(&app, "Please enter the bank's title.", 300, 90);
222        newbank.associated_textbook = input_string(&app, "If you are using an associated textbook \n please enter its info. \n Press  Enter  if no textbook is being used.",
223                                                   800, 200);
224        // Pass the new bank into CURRENT_BANK
225        {
226            *CURRENT_BANK.lock().unwrap() = newbank.clone();
227        }
228
229        // Save and display the bank.
230        bnk_save();
231
232        // Create widgets based on the data in CURRENT_BANK.
233        //make_title_txtedtr();
234        //make_scrollgroup();
235        //make_question_boxes();
236    }
237
238    /// Reads a question bank's data from a file.
239    ///
240    pub fn bnk_read() {
241
242        // todo: Check for bank in memory before proceeding.
243
244        // region Set up directories.
245
246                    // Setup proper directory and read the file.
247        println!("\n Please choose the Bank file to be read.");
248
249        let usepath: String;
250
251        { // Global variable scope is restricted to avoid Mutex lock.
252            if LAST_DIR_USED.lock().unwrap().clone() == "" {
253                *LAST_DIR_USED.lock().unwrap() = BANK_DIR.to_string().clone();
254            }
255            let lastdir = LAST_DIR_USED.lock().unwrap().clone();
256            usepath = file_fullpath(&lastdir);
257            *LAST_DIR_USED.lock().unwrap() = usepath.clone();  // Update LAST_DIR_USED
258        }
259        //endregion
260
261        // region Read the chosen file.
262        let usebank: Bank;
263        match file_read_to_string(&usepath) {
264            Ok(contents) => {
265                usebank = serde_json::from_str(&contents).unwrap();
266                *CURRENT_BANK.lock().unwrap() = usebank.clone();
267            }
268            // TODO: Fix error handling.  This is terrible.  See thread in forum at
269            // https://users.rust-lang.org/t/help-understanding-never-used-warning/125562/2
270            Err(err) => {
271                eprintln!("\n Error reading the file: {} \n", err);
272                panic!("\n Error reading the file. \n");
273            }
274        }
275        // endregion
276
277        {
278            *CURRENT_BANK.lock().unwrap() = usebank.clone();
279        }  // Pass the new bank into CURRENT_BANK
280    }
281
282    /// Refreshes the contents of the title box of a bank's display.
283    ///
284    pub fn bnk_refresh_title() {
285        let usebank: Bank;
286        let mut wdgts: Wdgts;
287        {
288            usebank = CURRENT_BANK.lock().unwrap().clone();
289            wdgts = WIDGETS.lock().unwrap().clone();
290        }  // Access global structs.
291
292        let mut buf = TextBuffer::default();
293        buf.set_text(usebank.bank_title.as_str());  // Uses the title from the current bank.
294        wdgts.title_editbox.set_buffer(buf);
295
296        //  let title_text =   // There is likely to be a use for  title_text   in the future.
297        wdgts.title_editbox.buffer().unwrap().text();
298    }
299
300    /// Prepares a Bank struct for saving.
301    ///
302    pub fn bnk_save() {
303        // region TODO's
304        // TODO: Find way to insert bank title into the save-file dialog.
305        // TODO: Find way to append correct extension automatically.
306        // endregion
307
308        if LAST_DIR_USED.lock().unwrap().clone() == "" {
309            *LAST_DIR_USED.lock().unwrap() = BANK_DIR.to_string().clone();
310        }  // If no bank loaded, use default.
311
312        let lastdir: String;
313        {
314            lastdir = LAST_DIR_USED.lock().unwrap().clone();
315        }
316
317        println!("Please choose a directory and file name for saving. \n");
318        let usepath = file_browse_save_fltr(&lastdir, "*.bnk");
319
320        {
321            *LAST_DIR_USED.lock().unwrap() = usepath.clone();
322        }  // Set the last used directory.
323
324        bnk_save_as_json(&usepath);
325    }
326
327    /// Saves a Bank struct to a file in json format.
328    ///
329    pub fn bnk_save_as_json(usepath: &String) {
330
331        let usebank;
332        {
333            usebank = CURRENT_BANK.lock().unwrap().clone();
334        }
335
336        let bnk_as_json = serde_json::to_string(&usebank).unwrap();  // Convert bank to json string.
337
338        let mut file = File::create(usepath).expect("Could not create file!");
339
340        file.write_all(bnk_as_json.as_bytes())
341            .expect("Cannot write to the file!");
342    }
343
344    /// Recalculates the variables in the questions of a Bank.
345    ///
346    pub fn bnk_recalc() {
347        let usebank;
348        {
349            usebank = CURRENT_BANK.lock().unwrap().clone();
350        }
351
352        println!("\n Recalculate variables in a bank.  Not yet implemented. \n");
353        println!("\n The current bank is: \n {:?} \n", usebank);
354
355        //  Read the passed bank
356        //  for q in usebank.question_vec.iter() {
357        //      step thru and recalc each variable
358        //      in each question.
359        // }
360    }
361
362    /*
363    pub fn test_globalbank_access() {
364        let testbank = CURRENT_BANK.lock().unwrap().clone();
365        println!("\n The test bank is: \n {:?} \n", testbank);
366    }
367
368
369    pub fn temp_listwindows(app: &App) {
370
371    // Retrieve all open child windows
372    let windows = app::windows();
373    println!("\n Currently Open Child Windows: \n");
374
375    for item in windows.iter() {
376        let winlabel = item.label();
377        println!("\n Window: {} \n", winlabel);
378    }
379}
380*/  // delete later
381
382}  // End    bank    module
383
384/// Functions that deal with the Question struct.
385///
386pub mod questions {
387    use crate::banks::{bnk_refresh_widgets, bnk_save, Bank};
388    use crate::variable::*;
389    use crate::{APP_FLTK, CURRENT_BANK, LAST_DIR_USED, VARIABLE_DIR};
390    use fltk::app::set_font_size;
391    use fltk::enums::{Color, Shortcut};
392    use fltk::prelude::{DisplayExt, GroupExt, MenuExt, WidgetBase, WidgetExt, WindowExt};
393    use fltk::text::{TextBuffer, TextEditor};
394    use fltk::{app, menu, text, window};
395    use lib_file::file_fltk::*;
396    use lib_file::file_mngmnt::{file_get_dir_list};
397    use lib_myfltk::fltkutils::*;
398    use lib_myfltk::input_fltk::{input_string, input_strvec};
399    use lib_utils::utilities::*;
400    use serde::{Deserialize, Serialize};
401
402    //region Struct Section
403
404    /// The second layer of the three structs QBC is built around.
405    /// Note that the field `var_vec: Vec<Variable>` is a vector
406    /// of type `Variable`.  `Variable` is the third and innermost layer in the
407    /// three-struct nest.
408    #[derive(Debug, Serialize, Deserialize, Clone)]
409    pub struct Question {
410        pub qtext: String,
411        pub var_dirpath: String,
412        pub var_vec: Vec<Variable>,
413        pub answer: String,
414        pub objectives: Vec<String>,
415        pub prereqs: Vec<String>,
416    }
417
418    impl Question {
419
420        /// Initialize a new Question.
421        fn new() -> Question {
422            Self {
423                qtext: "Please enter the text of your question.  Use real values. You will replace those values with variables later.  Be sure to delete these instructions before entering your question text.".to_string(),
424                var_dirpath: VARIABLE_DIR.to_string(),
425                var_vec: Vec::new(),
426                answer: "Answer".to_string(),
427                objectives: Vec::new(),
428                prereqs: Vec::new(),
429            }
430        }
431    }  // End   Question   impl
432    //endregion
433
434    /// Create a new question.
435    /// 
436    pub fn qst_create() {
437    // todo: The answer will need to parse inserted variables.
438
439        let mut newquest = Question::new();
440
441        // region Question data entry
442
443        let nowtext = qst_editor(newquest.qtext.as_str(), "Question Editor");
444        newquest.qtext = nowtext.clone();
445
446        // Pull the flagged variables from the text and push them to the variable vector.
447        qst_fill_varvec_parsetext(&mut newquest);
448
449                // Answer will eventually need to be calculated.
450
451        let app;
452        {
453            app = APP_FLTK.lock().unwrap().clone();
454        }
455
456        newquest.answer = input_string(&app, "Please input the question's answer:  ", 790, 300);
457        newquest.objectives = input_strvec(&app, "Please enter the question objectives:  ", 790, 300);
458        newquest.prereqs = input_strvec(&app, "Please enter the question prerequisites:  ", 790, 300);
459        // endregion
460
461// region Save and store the data
462        let mut usebank: Bank;
463        {
464            usebank = CURRENT_BANK.lock().unwrap().clone();
465        }  // Access the global Bank variable
466        usebank.question_vec.push(newquest);  // Store the new question in the bank
467        {  // Pass the modified bank into the global variable.
468            *CURRENT_BANK.lock().unwrap() = usebank.clone();
469        }
470        bnk_save();
471// endregion
472
473    }
474
475    /// Edit a question.
476    /// 
477    pub fn qst_edit(qst_idx: usize) {
478
479        let app;
480        let mut usebank;
481        {
482            app = APP_FLTK.lock().unwrap().clone();
483            usebank = CURRENT_BANK.lock().unwrap().clone();
484        }  // Access global variables.
485
486        let mut editqst = usebank.question_vec.get(qst_idx).unwrap().clone();
487
488        let nowtext = qst_editor(editqst.qtext.as_str(), "Question Editor");
489        editqst.qtext = nowtext.clone();
490
491        // Pull the flagged variables from the text and push them to the variable vector.
492        qst_fill_varvec_parsetext(&mut editqst);  // Need to clear the vector first.
493
494        // Answer will eventually need to be calculated.
495
496        editqst.answer = input_string(&app,"Please input the question's answer:  ", 790, 300);
497        editqst.objectives = input_strvec(&app,"Please enter the question objectives:  ", 790, 300);
498        editqst.prereqs = input_strvec(&app,"Please enter the question prerequisites:  ", 790, 300);
499
500        // Push the question to the vector in the bank and save the bank.
501        //let mut usebank = CURRENT_BANK.lock().unwrap();
502
503        // todo: This won't work.  push()  appends to the end of the vector. Fix it.
504        usebank.question_vec.push(editqst.clone());
505        bnk_save();
506        bnk_refresh_widgets();
507    }
508
509
510    /// // Is this necessary now?
511    /// 
512    pub fn qst_chooseqst() -> Question {
513
514        // TODO: Instead of trying to put the whole text of the question
515        //          body in the radio button, number each question in the
516        //          bank display and choose by the question number.
517
518        // Note:  This function may not be necessary.
519
520        let mut usevec: Vec<String> = Vec::new();
521
522        let usebank;
523        {
524            usebank = CURRENT_BANK.lock().unwrap().clone();
525        }
526
527        for item in usebank.question_vec.iter() {
528            usevec.push(item.qtext.clone());
529        }
530
531        let usequest = fltk_radio_lightbtn_menu(&usevec, "");
532        let mut editquest = Question::new();
533
534        for item in usebank.question_vec.iter() {
535            if item.qtext == usequest {
536                editquest = item.clone();
537            }
538        }
539
540        editquest
541    }
542
543
544    /// Parses the text of a question looking for flags that mark
545    /// variables.  Separates out the flagged variables, reading the
546    /// data into Variable structs and saving them in the
547    /// `quest.var_vec` vector field of the current question.
548    pub fn qst_fill_varvec_parsetext(quest: &mut Question) {
549        // region Create a vector of the variable names that have been flagged in the text.
550        let mut usevec = util_flaggedtxt_2vec(&quest.qtext, '§');
551        usevec.sort();
552        usevec.dedup();     // Remove repeats of the flagged variable names.
553        // endregion
554
555        // region Read the variable files from disk and insert them into the variable vector.
556        quest.var_vec.clear();
557        for _item in usevec {
558            let newvar = vrbl_read();
559            quest.var_vec.push(newvar);
560        }
561        // endregion
562    }
563
564    /// Calls up an FLTK TextEditor for entering/editing question text
565    /// and variables.
566    pub fn qst_editor(startertxt: &str, winlabel: &str) -> String {
567
568        let mut buf = TextBuffer::default();
569        let mut edtrwin = window::Window::default().with_size(800, 300);
570        set_font_size(20);
571        edtrwin.set_color(Color::Blue);
572        edtrwin.set_label(winlabel);
573        edtrwin.make_resizable(true);
574
575        buf.set_text(startertxt);
576        let mut edtr = TextEditor::default()
577            .with_size(770, 222)
578            .center_of_parent();
579
580        qst_editor_menubar(&edtr, &mut edtrwin, &mut buf);
581
582        edtr.set_buffer(buf.clone());   // Clone is used here to avoid an ownership error.
583        edtr.wrap_mode(text::WrapMode::AtBounds, 0);
584        edtr.set_color(Color::White);
585        edtr.set_text_size(22);
586        edtr.set_text_color(Color::Black);
587
588        edtrwin.end();
589        edtrwin.show();
590
591        while edtrwin.shown() {
592            app::wait();
593        }
594
595        println!("\n W5:  End of qst_editor().  The quesion text is:  {} \n", buf.text());
596
597        buf.text()
598    }
599
600    /// Menu bar for the `qst_editor`.
601    /// 
602    pub fn qst_editor_menubar(edtr: &TextEditor, edtrwin: &mut window::Window, buf: &mut TextBuffer) -> menu::MenuBar {
603        let mut menubar = menu::MenuBar::new(0, 0, edtrwin.width(), 40, "");
604
605        // region  "Finished" menu item
606        let mut edtrwin_clone = edtrwin.clone();
607        let quit_idx = menubar.add(
608            "Finished\t",
609            Shortcut::None,
610            menu::MenuFlag::Normal,
611            move |_| {
612                edtrwin_clone.hide();
613            },
614        );
615        menubar.at(quit_idx).unwrap().set_label_color(Color::Red);
616        // endregion
617
618        // region "Insert Variable" menu item
619        let edtr_clone = edtr.clone();
620        let mut buf_clone = buf.clone();
621        menubar.add(
622            "Insert_Variable\t",
623            Shortcut::None,
624            menu::MenuFlag::Normal,
625            move |_| {
626                let newtext  = qst_make_var_replace_text();
627                fltk_replace_highlighted_text(&edtr_clone, &mut buf_clone, &newtext);
628            },
629        );
630        // endregion
631
632        menubar
633    }
634
635    /// This function is called when the user highlights text that is
636    /// to be replaced by a variable. This function uses the variable name
637    /// to create text -- between flags -- that then replaces the highlighted text.
638    /// Returns the replacement text.
639    pub fn qst_make_var_replace_text() -> String {
640
641        // todo: Allow for user to input a more readable variable name
642        //          than the name of the variable file name on disk.
643
644        let usedir = VARIABLE_DIR.to_string();
645
646        println!("Please choose the variable you want to insert. \n");
647
648        let path = file_pathonly(&usedir);
649        {
650            LAST_DIR_USED.lock().unwrap().clone_from(&path);  // Refresh LAST_DIR_USED
651        }
652
653        let flist = file_get_dir_list(&path);
654        let varname = fltk_radio_lightbtn_menu(&flist, "");
655        let rpltxt = format!("§{}§", varname);
656
657        rpltxt
658    }
659
660    /*
661    /// This is no longer needed.
662    pub fn qst_read() -> Question {
663
664        // region Choose the desired path.
665        let mut usedir = String::new();
666        {
667            usedir = LAST_DIR_USED.lock().unwrap().clone();
668        }
669        println!("\n Please choose the Question file to be read.");
670        let usepath = file_fullpath(&usedir);
671        // endregion
672
673        match file_read_to_string(&usepath) {
674            Ok(contents) => {
675                let newquest = serde_json::from_str(&contents).unwrap();
676                newquest
677            }
678            Err(err) => {
679                eprintln!("\n Error reading the file: {} \n", err);
680                panic!("\n Error reading the file. \n");
681            }
682        }
683
684    }
685
686
687    /*
688    pub fn qst_fill_varvec_dirlist() -> Vec<Variable> {
689        println!("\n Please choose the variables you want to include as part of your question:  ");
690        let path = file_pathonly();
691        let flist = file_get_dir_list(&path);
692        let flist_vec = chkbox_shift_menu(&flist);
693        let mut usevec: Vec<Variable> = Vec::new();
694
695        for item in flist_vec {
696            let flist_fullpath = format!("{}/{}", path, item);
697            println!("{}", flist_fullpath);
698            usevec.push(vrbl_read_nogetpath(&flist_fullpath));
699        };
700        usevec
701    }
702*/   //fn qst_fill_varvec_dirlist()  --  delete later.
703
704     */  // Delete later.
705
706
707    /*
708        -- Answers will be calculated from the current variable values.
709
710        -- What are you going to do about operators and how they interact, especially
711            when the operator is given in the question in verbal format?
712            -- The answer equation will have to be entered by the user.
713
714        -- And then there be equations!!!!
715
716     */  // Issues & questions
717
718}  // End   questions   module
719
720/// Functions that deal with the Variable struct.
721///
722pub mod variable {
723
724    use crate::global::{TypeWrapper, TypeWrapper::*};
725    use crate::{lists::*, math_functions::*, LAST_DIR_USED, VARIABLE_DIR};
726    use lib_file::{file_fltk::*, file_mngmnt::*};
727    use lib_utils::vec::*;
728    use serde::{Deserialize, Serialize};
729    use std::{fs::File, io::Write};
730    use std::cell::RefCell;
731    use std::rc::Rc;
732    use fltk::app;
733    use fltk::button::{Button, CheckButton, RadioLightButton};
734    use fltk::enums::{Color, FrameType};
735    use fltk::{frame::Frame, group::Group, window::Window};
736    use fltk::input::{FloatInput, IntInput};
737    use fltk::prelude::{ButtonExt, GroupExt, InputExt, WidgetBase, WidgetExt, WindowExt};
738
739    //region Struct Section
740
741    /// The third and innermost layer of the three struct nest 
742    /// that QBC is built around.
743    #[derive(Debug, Serialize, Deserialize, Clone)]
744    pub struct Variable {
745        pub var_fname: String,  
746        pub params: VarPrmtrs,
747        pub list_fname: String,
748        pub content: TypeWrapper,
749        pub var_type: String,
750    }
751
752    impl Variable {
753
754        /// Initialize a new Variable.
755        pub fn new() -> Variable {
756            Self {
757                var_fname: "new_variable".to_string(),
758                params: VarPrmtrs::new(),
759                list_fname: "".to_string(),
760                content: Integer(0),
761                var_type: "Strings".to_string(),   // "Strings", "chars", "ints", "floats"
762            }
763        }
764    } // End Variable impl
765
766    /// Struct that holds the parameters that determine the behavior
767    /// of a Variable.
768    #[derive(Debug, Serialize, Deserialize, Clone)]
769    pub struct VarPrmtrs {
770        pub is_string: bool,
771        pub is_char: bool,
772        pub is_from_list: bool,
773        pub is_int: bool,
774        pub is_float: bool,
775        pub num_min_int: i64,
776        pub num_max_int: i64,
777        pub num_min_float: f64,
778        pub num_max_float: f64,
779        pub num_dcml_places: usize,
780        pub num_comma_frmttd: bool,
781    }
782
783    impl VarPrmtrs { 
784        /// Creates a new variable.  Sets the
785        /// default variable type to `int` and all ranges to 0.
786        pub fn new() -> VarPrmtrs {
787            Self {
788                is_string: false,
789                is_char: false,
790                is_from_list: false,
791                is_int: true,
792                is_float: false,
793                num_min_int: 0,
794                num_max_int: 0,
795                num_min_float: 0.0,
796                num_max_float: 0.0,
797                num_dcml_places: 0,
798                num_comma_frmttd: false,  // Leave implementing this until you need to output it.
799
800                // Default values all assume that the variable is an i64.
801            }
802        }
803        
804    } // ~~~~~ End VarPrmtrs impl ~~~~~
805    //endregion
806
807    /// Create a new variable.
808    /// 
809    pub fn vrbl_create() {
810        
811        //todo: Instead of passing a reference to the variable struct,
812        //          have the parameters box return the variable struct.
813        
814        let mut var1 = Variable::new();
815
816        vrbl_parameters_input_box(&mut var1);
817
818        println!("\n W3 -- Back in vrbl_create():  The Variable struct now contains:  {:?} \n", var1);
819                
820        vrbl_save(&mut var1);
821    }
822
823    /// Input and save a new variable's parameters into the Variable struct.
824    /// 
825    pub fn vrbl_parameters_input_box(var1: &mut Variable) {
826        
827        // todo: Grey out the input fields when the variable type is not "int" or "float".
828        //          Use the `deactivate()` method.  First attempt didn't work.
829        
830        let mut win = Window::new(900, 100, 600, 400, "Variable Parameters");
831        win.set_color(Color::Cyan);
832        win.make_resizable(true);
833       
834        // region Create the radio buttons for the variable type.
835        let radio_group = Group::new(0, 0, 600, 50, None);
836
837        // Create horizontal radio light buttons across the top -- initial spacing.
838        let bttn_w = 120;
839        let bttn_h = 30;
840        let spacing = 20;
841        let types_xxx = 40;
842        let types_yyy = 20;
843
844        let mut strings_btn = RadioLightButton::new(types_xxx, types_yyy, bttn_w, bttn_h, "Strings");
845        let chars_btn = RadioLightButton::new(types_xxx + bttn_w + spacing, types_yyy, bttn_w, bttn_h, "Characters");
846        let ints_btn = RadioLightButton::new(types_xxx + 2 * (bttn_w + spacing), types_yyy, bttn_w, bttn_h, "Integers");
847        let decimals_btn = RadioLightButton::new(types_xxx + 3 * (bttn_w + spacing), types_yyy, bttn_w, bttn_h, "Decimals");
848
849        // Set Integers as default selection
850        strings_btn.set_value(true);
851
852        radio_group.end();
853        // endregion
854
855        // region Create "comma" & "list" check boxes in row below the radio buttons.
856
857            // Calculate the position & size of the check boxes.
858        let ckbx_y = types_yyy + bttn_h + 20;  // Position below radio buttons
859        let ckbx_w = 150;
860        let ckbx_h = 25;
861        let ckbx_spacing = 55;
862
863        let total_radio_width = bttn_w * 4 + spacing * 2;  // Width of all radio buttons + spacing
864        let start_x = types_xxx + (total_radio_width - (ckbx_w * 2 + ckbx_spacing)) / 2;
865            
866            // Create the check boxes.
867        let usecommas = CheckButton::new(start_x, ckbx_y, ckbx_w, ckbx_h, "Comma Formatted");
868        let fromlist = CheckButton::new(start_x + ckbx_w + ckbx_spacing, ckbx_y,
869                                        ckbx_w, ckbx_h, "Value to come from a List");
870        
871
872        // endregion
873
874        // region Set up frames -- for Integer & Decimal parameter entry. 
875
876        // region Set up frame parameters
877        let frame_y = ckbx_y + ckbx_h + 20;  // Position below checkboxes
878        let frame_w = 250;
879        let input_w = 100;
880        let input_h = 25;
881        let label_h = 20;
882        let field_spacing = 10;
883        let frame_spacing = 20;
884        let frame_h = 30 + (3 * (label_h + input_h + field_spacing)) + 15;
885        // endregion
886
887        // region Create Integers frame & input fields
888        let mut int_frame = Group::new(types_xxx, frame_y, frame_w, frame_h, None);
889        let mut int_label = Frame::new(types_xxx, frame_y, frame_w, 30, "Integer Parameters");
890        int_label.set_label_size(14);
891
892        // Calculate centered position for input fields in integer frame
893        let int_input_x = types_xxx + (frame_w - input_w) / 2;
894        let int_first_y = frame_y + 35; // Start below the frame label
895
896        // Integer Minimum Value
897        let _intmin_label = Frame::new(int_input_x, int_first_y, input_w, label_h, "Minimum Value");
898        let mut intmin = IntInput::new(int_input_x, int_first_y + label_h, input_w, input_h, "");
899
900        // Integer Maximum Value
901        let _intmax_label = Frame::new(int_input_x, int_first_y + label_h + input_h + field_spacing,
902                                       input_w, label_h, "Maximum Value");
903        let mut intmax = IntInput::new(int_input_x, int_first_y + label_h + input_h + field_spacing + label_h,
904                                   input_w, input_h, "");
905
906        int_frame.set_frame(FrameType::DownBox);   // Add frame border
907        int_frame.end();
908        // endregion
909
910        // region Create Decimals frame & input fields
911        let mut decimal_frame = Group::new(types_xxx + frame_w + frame_spacing, frame_y, frame_w, frame_h, None);
912        let mut decimal_label = Frame::new(types_xxx + frame_w + frame_spacing, frame_y, frame_w, 30, "Decimal Parameters");
913        decimal_label.set_label_size(14);
914
915        // Calculate centered position for input fields in decimal frame
916        let dec_input_x = types_xxx + frame_w + frame_spacing + (frame_w - input_w) / 2;
917        let dec_first_y = frame_y + 35; // Start below the frame label
918
919        // Decimal Minimum Value
920        let _decmin_label = Frame::new(dec_input_x, dec_first_y, input_w, label_h, "Minimum Value");
921        let mut decmin = FloatInput::new(dec_input_x, dec_first_y + label_h, input_w, input_h, "");
922
923        // Decimal Maximum Value
924        let _decmax_label = Frame::new(dec_input_x, dec_first_y + label_h + input_h + field_spacing,
925                                       input_w, label_h, "Maximum Value");
926        let mut decmax = FloatInput::new(dec_input_x, dec_first_y + label_h + input_h + field_spacing + label_h,
927                                     input_w, input_h, "");
928
929        // Decimal Places
930        let _decplaces_label = Frame::new(dec_input_x, dec_first_y + 2 * (label_h + input_h + field_spacing),
931                                          input_w, label_h, "Decimal Places");
932        let mut decplaces = IntInput::new(dec_input_x, dec_first_y + 2 * (label_h + input_h + field_spacing) + label_h,
933                                      input_w, input_h, "");
934
935        decimal_frame.set_frame(FrameType::DownBox);   // Add frame border
936        decimal_frame.end();
937        // endregion
938
939        // endregion
940
941        // region Create the Submit button
942        let submit_btn_w = 100;
943        let submit_btn_h = 40;
944
945        // Calculate center position based on the frames
946        let total_frames_width = frame_w * 2 + frame_spacing;
947        let submit_btn_x = types_xxx + (total_frames_width - submit_btn_w) / 2;
948        let submit_btn_y = frame_y + frame_h + 20;  // 20 pixels gap after frames
949
950        let mut submit_btn = Button::new(submit_btn_x, submit_btn_y, submit_btn_w, submit_btn_h, "Submit");
951        // endregion
952
953        win.end();
954        win.show();
955               
956        // region Clone variables for the callback
957        let strings_btn = strings_btn.clone();
958        let chars_btn = chars_btn.clone();
959        let ints_btn = ints_btn.clone();
960        let decimals_btn = decimals_btn.clone();
961        let mut win_clone = win.clone();
962        
963        let datavar = Rc::new(RefCell::new(Variable::new()));
964        let datavar_outside = datavar.clone();  // Create a second Rc pointing to the same RefCell
965
966
967        // endregion
968
969        // region Do the callback for the Submit button
970        submit_btn.set_callback(move |_| {
971
972            // region Deal with the radio buttons.
973            let vartype = if strings_btn.value() {
974                decmin.deactivate();
975                decmax.deactivate();
976                decplaces.deactivate();
977                intmin.deactivate();
978                intmax.deactivate();
979                "Strings"
980            } else if chars_btn.value() {
981                decmin.deactivate();
982                decmax.deactivate();
983                decplaces.deactivate();
984                intmin.deactivate();
985                intmax.deactivate();
986                "Characters"
987            } else if ints_btn.value() {
988                decmin.deactivate();
989                decmax.deactivate();
990                decplaces.deactivate();
991                "Integers"
992            } else if decimals_btn.value() {
993                intmin.deactivate();
994                intmax.deactivate();
995                "Decimals"
996            } else {
997                "None"
998            };
999           
1000            datavar.borrow_mut().var_type = vartype.to_string();
1001            // endregion
1002
1003            //region Deal with the "comma" & "list" check boxes.
1004            if usecommas.is_checked() {
1005                datavar.borrow_mut().params.num_comma_frmttd = true;
1006                print!("\n Comma Formatted == true \n");
1007            }  else {
1008                datavar.borrow_mut().params.num_comma_frmttd = false;
1009                print!("\n Comma Formatted == false \n");
1010            }
1011
1012            if fromlist.is_checked() {
1013                datavar.borrow_mut().params.is_from_list = true;
1014                print!("\n List == true \n");
1015            }  else {
1016                datavar.borrow_mut().params.is_from_list = false;
1017                print!("\nList == false \n");
1018            }
1019            // endregion
1020
1021            // region Deal with the Integer input fields.
1022            if vartype == "Integers" {
1023                datavar.borrow_mut().params.is_int = true;
1024                datavar.borrow_mut().params.is_float = false;
1025                datavar.borrow_mut().params.num_min_int = intmin.value().parse::<i64>().unwrap();
1026                datavar.borrow_mut().params.num_max_int = intmax.value().parse::<i64>().unwrap();
1027            }
1028            // endregion
1029
1030            // region Deal with the Decimal input fields.
1031            if vartype == "Decimals" {
1032                datavar.borrow_mut().params.is_int = false;
1033                datavar.borrow_mut().params.is_float = true;
1034                datavar.borrow_mut().params.num_min_float = decmin.value().parse::<f64>().unwrap();
1035                datavar.borrow_mut().params.num_max_float = decmax.value().parse::<f64>().unwrap();
1036                datavar.borrow_mut().params.num_dcml_places = decplaces.value().parse::<usize>().unwrap();
1037            }
1038            // endregion
1039
1040            println!("\n In the callback, datavar == {:?} \n", datavar);
1041       
1042            // Close the window
1043            win_clone.hide();
1044        });
1045        // endregion
1046
1047        // Keep window active until hidden
1048        while win.shown() {
1049            app::wait();
1050        }
1051        
1052        *var1 = datavar_outside.borrow().clone();
1053    }
1054
1055    
1056/*
1057    /// Set the parameters for a Variable.
1058    /// 
1059    pub fn vrbl_input_parameters(data: &mut Variable) {  // Set boolean parameters only.  Leave data alone.
1060
1061        // todo: Turn all this into a window of radio and checkbox buttons for setting
1062        //          these parameters.
1063
1064        match data.var_type.as_str() {
1065            "Strings" => {  // Note that Strings should only come from a list.
1066                data.params.is_string = true;
1067                data.params.is_int = false;
1068                data.params.is_from_list = true;
1069            }
1070
1071            "Character" => {  // Note that characters also can only come from a list.
1072                data.params.is_char = true;
1073                data.params.is_int = false;
1074                data.params.is_from_list = true;
1075            }
1076
1077            "Integers" => {
1078                data.params.num_comma_frmttd = input_bool_prompt("\n Is the value to be comma formatted?   ");
1079
1080                let mini_choice = input_bool_prompt("\n Is the variable contents to come from a list?   ");
1081                if mini_choice {
1082                    data.params.is_from_list = true;
1083                    return;
1084                }
1085                data.params.num_min_int = input_num_prompt("\n Please enter the minimum int value:  ");
1086                data.params.num_max_int = input_num_prompt("\n Please enter the maximum int value:  ");
1087            }
1088
1089            "Decimals" => {
1090                data.params.is_int = false;
1091                data.params.num_dcml_places = input_num_prompt("\n How many decimal places are allowed?  ");
1092                data.params.num_comma_frmttd = input_bool_prompt("\n Is the value to be comma formatted?   ");
1093
1094                let mini_choice = input_bool_prompt("\n Is the variable contents to come from a list?   ");
1095                if mini_choice {
1096                    data.params.is_from_list = true;
1097                    return;
1098                }
1099                data.params.num_min_float = input_num_prompt("\n Please enter the minimum float value:  ");
1100                data.params.num_max_float = input_num_prompt("\n Please enter the maximum float value:  ");
1101            }
1102
1103            _ => { unreachable!(); }
1104        }
1105    }
1106
1107    /// Input more data to be used in a variable.
1108    /// 
1109    pub fn vrbl_input_vardata(data: &mut Variable) {
1110        data.var_fname = input_string_prompt("\n Please enter a title/filename for your new variable:  ");
1111        vrbl_setvalues(data);
1112    }
1113    */   // I think these can now be deleted.
1114    
1115    
1116
1117    /// Prepare a Variable for saving.
1118    /// 
1119    pub fn vrbl_save(var1: &mut Variable) {
1120        {
1121            if LAST_DIR_USED.lock().unwrap().clone() == "" {
1122                *LAST_DIR_USED.lock().unwrap() = VARIABLE_DIR.to_string().clone();
1123            }  // If no path loaded, use default.
1124        }  // Access the global variable.
1125        let lastdir = LAST_DIR_USED.lock().unwrap().clone();
1126        let usepath = file_browse_save_fltr(&lastdir, "Variable Files   \t*.vrbl\nText Files   \t*.txt\nList Files    \t*.lst\nAll Files    \t*.*");
1127
1128        {
1129            *LAST_DIR_USED.lock().unwrap() = usepath.clone();
1130        }  // Set LAST_DIR_USED to the new path.
1131
1132        var1.var_fname = file_path_to_fname(&usepath);
1133        vrbl_save_as_json(&var1, &usepath);
1134
1135        println!("\n The variable has been saved.");
1136    }
1137
1138    /// Save a Variable in json format.
1139    /// 
1140    pub fn vrbl_save_as_json(var: &Variable, usepath: &String) {
1141        let var_as_json = serde_json::to_string(var).unwrap();
1142
1143        let mut file = File::create(usepath.as_str()).expect("Could not create file!");
1144
1145        file.write_all(var_as_json.as_bytes())
1146            .expect("Cannot write to the file!");
1147    }
1148
1149    /// Read a variable from a file.
1150    /// 
1151    pub fn vrbl_read() -> Variable {
1152
1153        // region Choose the correct directory path
1154        let mut usepath = VARIABLE_DIR.to_string();
1155        println!("\n Please choose the variable file to be used.");
1156        usepath = file_fullpath(&usepath);
1157        {
1158            *LAST_DIR_USED.lock().unwrap() = usepath.clone();
1159        }  // Set LAST_DIR_USED to the new path.
1160        // endregion
1161
1162        match file_read_to_string(&usepath) {
1163            Ok(contents) => {
1164                let newvariable = serde_json::from_str(&contents).unwrap();
1165                newvariable
1166            }
1167            Err(err) => {
1168                eprintln!("\n Error reading the file: {} \n", err);
1169                panic!("\n Error reading the file. \n");
1170            }
1171        }
1172    }
1173
1174
1175
1176    /// Sets and calculates the values of non-boolean fields in the Variable struct.
1177    /// 
1178    pub fn vrbl_setvalues(var1: &mut Variable) {
1179        //let lastdir = LAST_DIR_USED.lock().unwrap();
1180
1181        if var1.params.is_from_list {  // The variable content is to come from a list.
1182            match var1.var_type.as_str() {
1183                "Strings" => {
1184                    println!("\n Please choose the list you want to use.");
1185                    let newlist = list_read("Strings");  // Returns a tuple (listname, List)
1186                    var1.list_fname = newlist.0;  // Sets the value of the variable's listname field.
1187
1188                    let usevec = newlist.1.words.clone();  // Clones the list content vector.
1189                    //let usevec_str = vec_string_to_str(&usevec);
1190                    let item = vec_random_choice(&usevec);
1191                    match item {
1192                        Some(x) => {
1193                            println!("\n The chosen item is:  {:?}", x);
1194                            var1.content = Alphanum(x.0.to_string());
1195                        },
1196                        None => panic!("No item was chosen."),
1197                    }
1198                },
1199
1200                "chars" => {
1201                    println!("\n Please choose a list to be read.");
1202                    let newlist = list_read("chars");
1203                    var1.list_fname = newlist.0;
1204
1205                    let item = vec_random_choice(&newlist.1.runes);
1206                    match item {
1207                        Some(x) => {
1208                            println!("\n The chosen item is:  {:?}", x);
1209                            var1.content = Letter(*x.0);
1210                        },
1211                        None => panic!("No item was chosen."),
1212                    }
1213                },
1214
1215                "ints" => {
1216                    println!("\n Please choose a list to be read.");
1217                    let newlist = list_read("ints");
1218                    var1.list_fname = newlist.0;
1219
1220                    let item = vec_random_choice(&newlist.1.intsigned);
1221                    match item {
1222                        Some(x) => {
1223                            println!("\n The chosen item is:  {:?}", x);
1224                            var1.content = Integer(*x.0);
1225                        },
1226                        None => panic!("No item was chosen."),
1227                    }
1228                },
1229
1230                "floats" => {
1231                    println!("\n Please choose a list to be read.");
1232                    let newlist = list_read("floats");
1233                    var1.list_fname = newlist.0;
1234
1235                    let item = vec_random_choice(&newlist.1.decimals);
1236                    match item {
1237                        Some(x) => {
1238                            println!("\n The chosen item is:  {:?}", x);
1239                            var1.content = Floating(*x.0);
1240                        },
1241                        None => panic!("No item was chosen."),
1242                    }
1243                },
1244
1245                _ => {}
1246            }
1247        } else {
1248            if var1.params.is_int {  // Numeric values will always be randomly generated.
1249                let numint: i64 = math_gen_random_num(var1.params.num_min_int, var1.params.num_max_int);
1250                var1.content = Integer(numint);
1251            } else {  // The content is a float.
1252                let mut numfloat: f64 = math_gen_random_num(var1.params.num_min_float, var1.params.num_max_float);
1253                numfloat = math_round_to_place_f64(&numfloat, var1.params.num_dcml_places);
1254                var1.content = Floating(numfloat);
1255            }
1256        }
1257    }
1258
1259
1260    /// Recalculates the values in the non-boolean fields of a Variable struct.
1261    /// 
1262    pub fn vrbl_recalc() {
1263        let mut usevar = vrbl_read();
1264
1265        println!("\n The variable before recalc is: \n {:?}", usevar);
1266
1267        vrbl_setvalues(&mut usevar);
1268        vrbl_save(&mut usevar);
1269
1270        println!("\n The variable after recalc is: \n {:?} \n", usevar);
1271
1272    }
1273
1274
1275
1276} // End   variable   module
1277
1278/// Functions for creating and manipulating lists.
1279///
1280pub mod lists {
1281    use std::{fs::File, io::Write};
1282    use serde::{Deserialize, Serialize};
1283    use lib_file::file_fltk::{file_browse_save_fltr, file_fullpath_fltr};
1284    use lib_file::file_mngmnt::file_read_to_string;
1285    use lib_myfltk::input_fltk::*;
1286    use crate::{APP_FLTK, LAST_DIR_USED, VARIABLE_DIR};
1287
1288    // region Struct section
1289
1290    /// Contains a vector field for each of the four data types
1291    /// allowed in lists associated with QBC.
1292    #[derive(Debug, Serialize, Deserialize)]
1293    pub struct List {
1294        pub words: Vec<String>,
1295        pub runes: Vec<char>,
1296        pub intsigned: Vec<i64>,
1297        pub decimals: Vec<f64>,
1298        pub typechoice: String, // "Strings", "chars", "ints", "floats"
1299    }
1300
1301    impl List {
1302
1303        /// Initializes a new list struct.
1304        pub fn new() -> List {
1305            Self {
1306                words: Vec::new(),
1307                runes: Vec::new(),
1308                intsigned: Vec::new(),
1309                decimals: Vec::new(),
1310                typechoice: "Strings".to_string(),
1311            }
1312        }
1313    }  // ----------  End List impl ----------
1314
1315    // endregion
1316
1317    /// Create a new list.
1318    /// 
1319    pub fn list_create(typech: &str) {
1320        let mut newlist = List::new();
1321        newlist.typechoice = typech.to_string();
1322        
1323        let app;
1324        {
1325            app = APP_FLTK.lock().unwrap().clone();
1326        }
1327
1328        match typech {
1329            "String" | "Strings" => {     // String
1330                let uselist = input_strvec(&app, "Please enter a string.", 790, 300);
1331                newlist.words = uselist.clone();
1332                list_save(&newlist);
1333            }
1334
1335            "char" | "chars" => {     // char
1336                let uselist = input_charvec(&app, "Please enter a character." );
1337                newlist.runes = uselist.clone();
1338                list_save(&newlist);
1339            }
1340
1341            "int" | "ints" => {     // i64
1342                let uselist = input_i64vec(&app, "Please enter an integer." );
1343                newlist.intsigned = uselist.clone();
1344                list_save(&newlist);
1345            }
1346
1347            "float" | "floats" => {     // f64
1348
1349                let uselist = input_f64vec(&app, "Please enter a floating point number.");
1350                newlist.decimals = uselist.clone();
1351                list_save(&newlist);
1352            }
1353
1354            _ => {
1355                panic!("\n\n No match found!  Fix it!!\n\n");
1356            }
1357        }
1358
1359        // Note that the function saves the list, but does not return it.
1360    }
1361
1362    /// Read a list (in json format) from a file.  Returns a tuple (filename, List)
1363    /// containing the file name that was read along with the reconstituted list.
1364    pub fn list_read(typech: &str) -> (String, List) {
1365
1366        {
1367            if LAST_DIR_USED.lock().unwrap().clone() == "" {
1368                *LAST_DIR_USED.lock().unwrap() = VARIABLE_DIR.to_string().clone();
1369            }  // If no path loaded, use default.
1370        }  // Access the global variable.
1371        let lastdir = LAST_DIR_USED.lock().unwrap().clone();
1372        
1373        //let mut startdir = String::new();
1374        //{
1375            //startdir = LAST_DIR_USED.lock().unwrap().clone();
1376        //}
1377
1378        // TODO: Should this return an option or result rather than  `unwrap()` or `panic!()`?
1379        // TODO: This has not been tested after the last modifications were made.
1380        // todo: You aren't dealing with LAST_DIR_USED correctly.
1381        // todo: The returns of this function don't look right.  Check it out.
1382
1383        let readlist = loop {
1384            let usename = file_fullpath_fltr(&lastdir, "*.lst");
1385            *LAST_DIR_USED.lock().unwrap() = usename.clone();
1386
1387            match file_read_to_string(&usename) {
1388                Ok(contents) => {
1389                    let newlist = serde_json::from_str(&contents).unwrap();
1390                    let typchk = list_check_typematch(&newlist, typech);
1391                    if !typchk { continue }
1392                    else { break (usename, newlist)}
1393                }
1394                Err(err) => {
1395                    eprintln!("\n Error reading the file: {} \n", err);
1396                    panic!("\n Error reading the file. \n");
1397                }
1398            }
1399        };
1400
1401        readlist
1402    }
1403
1404    /// Edit an existing list.  Not yet implementd
1405    /// 
1406    pub fn list_edit() {
1407
1408        println!("\n Someday I'll write this function. \n");
1409    }
1410
1411    /// Prepare a list for saving to a file.
1412    /// 
1413    pub fn list_save(list: &List) -> String {
1414
1415        let startdir = LAST_DIR_USED.lock().unwrap().clone(); // Access the last used directory.
1416
1417        let path = file_browse_save_fltr(&startdir, "List Files    \t*.lst\nVariable Files   \t*.vrbl\nText Files   \t*.txt\nAll Files");
1418        *LAST_DIR_USED.lock().unwrap() = path.clone();  // Store the current path in global.
1419
1420        list_save_as_json(&list, &path);
1421
1422        path
1423    }
1424
1425    /// Save a list in json format.
1426    /// 
1427    pub fn list_save_as_json(list: &List, fname: &str) {
1428        let list_as_json = serde_json::to_string(list).unwrap();
1429
1430        let mut file = File::create(fname).expect("Could not create file!");
1431
1432        file.write_all(list_as_json.as_bytes())
1433            .expect("Cannot write to the file!");
1434    }
1435
1436    /// Check that a list contains the correct type of data.
1437    /// 
1438    pub fn list_check_typematch(uselist: &List, typech: &str) -> bool {
1439        if uselist.typechoice.as_str() != typech {
1440            println!("\n The data type of that list does not match your typechoice. \n");
1441            println!("Please choose a different list file. \n");
1442            false
1443        } else { true }
1444    }
1445
1446}  // End  lists module
1447
1448/// Functions for use in creating menus.
1449///
1450pub mod menus {
1451    use fltk::{app::quit, menu, window::Window};
1452    use fltk::enums::{Color, Shortcut};
1453    use fltk::prelude::{MenuExt, WidgetBase, WidgetExt};
1454    use crate::{banks::*, questions::*, variable::*, lists::*};
1455    use crate::misc::check_for_bank_loaded;
1456
1457    /// Create a menubar for the primary window.
1458    pub fn qbnk_menubar(primwin: &mut Window) -> menu::MenuBar {
1459        // todo: `primwin` is in the global Widgets variable.  Access it
1460        //          there rather than passing it to this function.
1461
1462        let mut menubar = menu::MenuBar::new(0, 0, primwin.width(), 40, "");
1463
1464        //region File section
1465
1466        menubar.add(
1467            "File/Print/Question Bank\t",
1468            Shortcut::None,
1469            menu::MenuFlag::Normal,
1470            |_| println!("Printing a new Question Bank."),
1471        );
1472        menubar.add(
1473            "File/Print/Question\t",
1474            Shortcut::None,
1475            menu::MenuFlag::Normal,
1476            |_| println!("Printing a new Question."),
1477        );
1478        menubar.add(
1479            "File/Print/Variable\t",
1480            Shortcut::None,
1481            menu::MenuFlag::Normal,
1482            |_| println!("Printing a new Variable."),
1483        );
1484        menubar.add(
1485            "File/Print/List\t",  // Where does versioning come in?
1486            Shortcut::None,
1487            menu::MenuFlag::Normal,
1488            |_| println!("Printing a new List."),
1489        );
1490        menubar.add(
1491            "File/Save\t", // Save always focuses on the Question Bank.
1492            Shortcut::None,
1493            menu::MenuFlag::Normal,
1494            |_| println!("Saving a Question Bank."),
1495        );
1496        menubar.add(
1497            "File/Save-as\t",
1498            Shortcut::None,
1499            menu::MenuFlag::Normal,
1500            |_| println!("Saving a Question Bank with a new name."),
1501        );
1502
1503        let quit_idx = menubar.add(
1504            "File/Quit\t",
1505            Shortcut::None,
1506            menu::MenuFlag::Normal,
1507            |_| {
1508                quit();
1509            },
1510        );
1511        menubar.at(quit_idx).unwrap().set_label_color(Color::Red);
1512        //endregion
1513
1514        //region Bank section
1515
1516        menubar.add(
1517            "Bank/New\t",
1518            Shortcut::None,
1519            menu::MenuFlag::Normal,
1520            move |_| {
1521                bnk_create();
1522                bnk_refresh_widgets();
1523            },
1524        );
1525
1526        menubar.add(
1527            "Bank/Open\t",
1528            Shortcut::None,
1529            menu::MenuFlag::Normal,
1530            move |_| {
1531                bnk_read();
1532                bnk_refresh_widgets();
1533            },
1534        );
1535
1536        // TODO: Add  ctrl-s  as option for saving.
1537        // TODO: Right now  Save  and  Save-As  are the same thing.
1538        //          Differentiate them.
1539
1540        menubar.add(
1541            "Bank/Save\t",
1542            Shortcut::None,
1543            menu::MenuFlag::Normal,
1544            |_| {
1545                bnk_save();
1546                println!("/n The question bank has been saved. \n")
1547            },
1548        );
1549        menubar.add(
1550            "Bank/Save-as\t",
1551            Shortcut::None,
1552            menu::MenuFlag::Normal,
1553            |_| {
1554                bnk_save();
1555                println!("/n The question bank has been saved. \n")
1556            },        );
1557
1558
1559        menubar.add(
1560            "Bank/Recalculate\t",
1561            Shortcut::None,
1562            menu::MenuFlag::Normal,
1563            |_| println!("\n Not yet implemented. \n"),
1564        );
1565
1566
1567        menubar.add(
1568            "Bank/Export\t",
1569            Shortcut::None,
1570            menu::MenuFlag::Normal,
1571            |_| println!("\n Not yet implemented. \n"),
1572        );
1573
1574        //endregion
1575
1576        //region Question Section
1577
1578        menubar.add(
1579            "Question/New\t",
1580            Shortcut::None,
1581            menu::MenuFlag::Normal,
1582            move |_| {
1583                if check_for_bank_loaded() {
1584                    qst_create();
1585                    bnk_refresh_widgets();
1586                }
1587
1588                // Keep the window display open after this function finishes.
1589              //  while primwin1.shown() {
1590              //      app::wait();
1591             //   }
1592            },
1593        );
1594
1595        menubar.add(
1596            "Question/Recalculate\t",
1597            Shortcut::None,
1598            menu::MenuFlag::Normal,
1599            |_| println!("Not yet implemented.  Recalculating dynamic content in a Question."),
1600        );
1601
1602        //endregion
1603
1604        //region Variable Section
1605
1606        menubar.add(
1607            "Variable/New\t",
1608            Shortcut::None,
1609            menu::MenuFlag::Normal,
1610            move |_| {
1611                vrbl_create();
1612            },
1613        );
1614        
1615        
1616        /*
1617        menubar.add(
1618            "Variable/New/String\t",
1619            Shortcut::None,
1620            menu::MenuFlag::Normal,
1621            move |_| {
1622                vrbl_create("Strings");
1623            },
1624        );
1625
1626        menubar.add(
1627            "Variable/New/Characters\t",
1628            Shortcut::None,
1629            menu::MenuFlag::Normal,
1630            move |_| {
1631                vrbl_create("chars");
1632            },
1633        );
1634
1635        menubar.add(
1636            "Variable/New/Integers\t",
1637            Shortcut::None,
1638            menu::MenuFlag::Normal,
1639            move |_| {
1640                vrbl_create("ints");
1641            },
1642        );
1643
1644        menubar.add(
1645            "Variable/New/Floats\t",
1646            Shortcut::None,
1647            menu::MenuFlag::Normal,
1648            move |_| {
1649                vrbl_create("floats");
1650            },
1651        );
1652
1653        menubar.add(
1654            "Variable/Recalculate\t",  // Does this make sense as a user task?  Yes, definitely.
1655            Shortcut::None,
1656            menu::MenuFlag::Normal,
1657            move |_| vrbl_recalc(),
1658        );
1659
1660        //endregion
1661        
1662         */  // Uneeded sub menu items.
1663        // endregion
1664
1665        //region List Section
1666
1667        menubar.add(
1668            "List/New/Strings\t",
1669            Shortcut::None,
1670            menu::MenuFlag::Normal,
1671            move |_| {
1672                list_create("Strings");
1673            },
1674        );
1675
1676        menubar.add(
1677            "List/New/Characters\t",
1678            Shortcut::None,
1679            menu::MenuFlag::Normal,
1680            move |_| {
1681                list_create("chars");
1682            },
1683        );
1684
1685        menubar.add(
1686            "List/New/Integers\t",
1687            Shortcut::None,
1688            menu::MenuFlag::Normal,
1689            move |_| {
1690                list_create("ints");
1691            },
1692        );
1693
1694        menubar.add(
1695            "List/New/Floats\t",
1696            Shortcut::None,
1697            menu::MenuFlag::Normal,
1698            move |_| {
1699                list_create("floats");
1700            },
1701        );
1702
1703        menubar.add(
1704            "List/Edit\t",
1705            Shortcut::None,
1706            menu::MenuFlag::Normal,
1707            |_| println!("\n List editing will be added in a later iteration. \n"),
1708        );
1709
1710        // -------  End list section
1711        //endregion
1712
1713        menubar
1714    }
1715} //  End   menus  module
1716
1717/// Math-based functions.
1718///
1719pub mod math_functions {
1720    use num_traits::pow;
1721    use rand::distributions::uniform::SampleUniform;
1722    use rand::{thread_rng, Rng};
1723
1724    /// Round an f64 to a given decimal place.
1725    ///
1726    pub fn math_round_to_place_f64(num: &f64, place: usize) -> f64 {
1727        let factor = pow(10, place);
1728        let rounded = (num * factor as f64).round() / factor as f64;
1729        rounded
1730    }
1731
1732    /// Generate and return a random number between the given min and max.
1733    ///
1734    /// Example:
1735    ///
1736    ///     fn main() {
1737    ///         let choice: i64 = math_gen_random_num(-9, 9);
1738    ///
1739    ///         println!("\n Your random number is:   {} \n", choice);
1740    ///     }
1741    ///
1742    pub fn math_gen_random_num<T: SampleUniform + PartialOrd>(min: T, max: T) -> T {
1743        let mut rng = thread_rng();
1744        rng.gen_range(min..=max)
1745    }
1746
1747} // End   math_functions   module
1748
1749/// Miscellaneous functions used by other modules.
1750///
1751pub mod misc {
1752    use crate::{banks::Bank, questions::qst_edit};
1753    use crate::{Wdgts, CURRENT_BANK, DEVELOPMENT_VERSION, PROGRAM_TITLE, QDISP_HEIGHT, SCROLLBAR_WIDTH, VERSION, WIDGETS};
1754    use fltk::prelude::{DisplayExt, GroupExt, WidgetBase, WidgetExt};
1755    use fltk::text::{TextBuffer, TextDisplay, TextEditor};
1756    use fltk::{text, window::Window};
1757    use fltk::{button::Button, enums::Color, group::Scroll};
1758
1759    /// Gets and returns the text from a given FLTK TextEditor.
1760    /// 
1761    pub fn get_text_from_editor(editor: &TextEditor) -> String {
1762        if let Some(buffer) = editor.buffer() {
1763            let text = buffer.text(); // Retrieve the text from the associated buffer
1764            return text;
1765        } else {
1766            String::new() // If no buffer is set, return an empty string
1767        }
1768    }
1769
1770    /// Sets up the primary window for QBC.
1771    /// 
1772    pub fn primwin_setup(primwin: &mut Window) {  // Set up the primary window.
1773        //let mut primwin = Window::default().with_size(825, 900).with_pos(1000, 100);
1774        primwin.set_color(Color::Blue);
1775        let fulltitle = format!("{} -- {} -- Version {}", DEVELOPMENT_VERSION, PROGRAM_TITLE, VERSION);
1776        primwin.set_label(fulltitle.as_str());
1777        primwin.make_resizable(true);
1778    }
1779
1780
1781    /*
1782    pub fn onopen_popup() -> Window {
1783        // On program opening, pop up a window with choice for new bank or open existing bank.
1784
1785        // region Set up button callback closures.
1786                // Button -- Create a new question bank.
1787        let bttn_newbank = move || {
1788            bnk_create();
1789            bnk_refresh_widgets();
1790        };
1791
1792                // Button -- Open an existing question bank.
1793        let bttn_openbank = move || {
1794            bnk_read();
1795            bnk_refresh_widgets();
1796        };
1797        // endregion
1798
1799        let mut wdgts = Wdgts::new();
1800        {
1801            wdgts = WIDGETS.lock().unwrap().clone();
1802        }  // Access the WIDGETS struct
1803
1804        // todo: The function below is way too complex.  Find another solution.
1805        let mut pop = fltk_popup_2btn(&wdgts.prim_win, Box::new(bttn_newbank), "Create new bank",
1806                                  Box::new(bttn_openbank), "Open existing bank");
1807        pop.set_color(Color::Red);
1808
1809        pop
1810    }
1811
1812     */  // No longer needed -- onopen_popup()
1813
1814
1815    /// Creates and sets up an FLTK TextEditor window to use for
1816    /// a title box when displaying a question Bank.
1817    pub fn make_title_txtedtr() {
1818
1819        // todo: Add a line, smaller font, below the main title for
1820        //      a subtitle or maybe the title of the textbook being used.
1821
1822        let usebank: Bank;
1823        let mut wdgts: Wdgts;
1824        {
1825            usebank = CURRENT_BANK.lock().unwrap().clone();
1826            wdgts = WIDGETS.lock().unwrap().clone();
1827        }  // Load the global structs.
1828
1829        let mut buf = TextBuffer::default();
1830        buf.set_text(usebank.bank_title.as_str());  // Uses the title from the current bank.
1831
1832        let mut ted = TextEditor::new(0, 40, wdgts.prim_win.width(), 60, "");
1833        ted.set_text_size(32);
1834        ted.set_text_color(Color::White);
1835        ted.set_color(Color::DarkMagenta);
1836        ted.set_buffer(buf.clone());   // Clone is used here to avoid an ownership error.
1837
1838        wdgts.title_editbox = ted.clone();  // Store the widgit in the widget struct.
1839        wdgts.prim_win.add(&ted.clone());
1840
1841        *WIDGETS.lock().unwrap() = wdgts.clone();    // Update the WIDGET global variable.
1842
1843        // todo: It would be nice to center and bold the text, but that is really
1844        //      difficult to do, so leave for later.
1845    }
1846
1847    /// Creates and sets up an FLTK scrollgroup to use
1848    /// when displaying a question Bank.
1849    pub fn make_scrollgroup() {
1850        let mut wdgts: Wdgts;
1851        let usebank: Bank;
1852        {
1853            wdgts = WIDGETS.lock().unwrap().clone();
1854            usebank = CURRENT_BANK.lock().unwrap().clone();
1855        }  // Access the global structs.
1856
1857        // Create scroll group
1858        let mut scroll = Scroll::new(0, wdgts.title_editbox.height() + 40,
1859                                     wdgts.prim_win.width(),
1860                                     usebank.question_vec.len() as i32 * QDISP_HEIGHT,
1861                                     "");
1862        scroll.set_scrollbar_size(SCROLLBAR_WIDTH);
1863
1864        // Add scroll to the Wdgts struct & window.
1865        wdgts.scroll = scroll.clone();
1866        wdgts.prim_win.add(&scroll.clone());
1867
1868        *WIDGETS.lock().unwrap() = wdgts.clone();    // Update the WIDGET global variable.
1869    }
1870
1871    /// Creates and sets up an FLTK TextDisplay boxes to use
1872    /// when displaying the questions in a question Bank.
1873    pub fn make_question_boxes() {
1874        // region TODO's
1875        //todo: The question numbers are displaying weird.  First question's label doesn't
1876        //          even show.  Work on that.  Next iteration.
1877        // TODO: Set up show/edit prereqs and objectives button
1878        // TODO: Create a subframe to display/edit the answer.
1879
1880        // endregion
1881
1882        let usebank: Bank;
1883        let mut wdgts: Wdgts;
1884        {
1885            usebank = CURRENT_BANK.lock().unwrap().clone();
1886            wdgts = WIDGETS.lock().unwrap().clone();
1887        }  // Access the global structs.
1888
1889        // region Calculate size and position values.
1890        let mut box_y = wdgts.title_editbox.height() + 1;   // Allow room for the Title Box
1891        box_y += 60;            // Allow room for the label of the first question box.
1892        let mut qnum = 1;       // Question number -- starts at 1.
1893        // endregion
1894
1895        // Set up a display box for each question in the bank.
1896        for item in usebank.question_vec.iter() {
1897
1898            // region Create the question label and set up text buffer.
1899            let qlabel = format!("Question {} :  ", qnum);
1900            let mut txtbuff = TextBuffer::default();
1901            txtbuff.set_text(item.qtext.as_str());
1902            // endregion
1903
1904            // region Setup the display box and it's attributes.
1905            let mut quest_disp = TextDisplay::new(0,
1906                                                  box_y,
1907                                                  wdgts.scroll.w() - SCROLLBAR_WIDTH,
1908                                                  QDISP_HEIGHT,
1909                                                  qlabel.as_str());
1910            quest_disp.set_buffer(txtbuff);
1911            quest_disp.wrap_mode(text::WrapMode::AtBounds, 0);
1912            quest_disp.set_color(fltk::enums::Color::White);
1913            quest_disp.set_text_size(22);
1914            quest_disp.set_text_color(fltk::enums::Color::Black);
1915            // endregion
1916
1917            // region Setup the edit button & callback. Buttons not added to widget struct.
1918            let editbtn_x = quest_disp.x() + quest_disp.w() - 65;  // Button is sized 50 X 30
1919            let editbtn_y = quest_disp.y() + quest_disp.h() - 35;
1920            let mut editbtn = Button::new(editbtn_x, editbtn_y, 50, 30, "Edit");
1921
1922            editbtn.set_callback(move |_| {
1923                println!("\n Edit button for Question #{} has been pressed. \n", qnum);
1924                qst_edit(qnum - 1);
1925            });
1926            // endregion
1927
1928            // region Increment values and push/add items to WIDGETS struct
1929            box_y += QDISP_HEIGHT;    // Increment the question display widget position.
1930            qnum += 1;       // Increment the question display number.
1931
1932            wdgts.qstn_boxes.push(quest_disp.clone());
1933            wdgts.scroll.add(&quest_disp);
1934            wdgts.scroll.add(&editbtn);
1935            // endregion
1936        }
1937        *WIDGETS.lock().unwrap() = wdgts.clone();    // Update the WIDGET global variable.
1938    }
1939
1940    /// Check to see whether or not a bank has been loaded into memory.
1941    /// 
1942    pub fn check_for_bank_loaded() -> bool {
1943        let usebank = CURRENT_BANK.lock().unwrap().clone();
1944        if usebank.bank_title.is_empty() {
1945            println!("\n No bank has been loaded. \n");  // todo: Find a non-terminal way to display this.
1946            println!(" Please open a bank. \n");
1947            false
1948        } else {
1949            true
1950        }
1951    }
1952
1953    /// Gets an FLTK window's position and dimension attributes.
1954    /// Here for debugging purposes only.
1955    pub fn get_window_attrs(win: &Window) {
1956        // For debugging purposes.  Move to  lib.utils???
1957
1958        let xxx = win.x();
1959        let yyy = win.y();
1960        let www = win.w();
1961        let hhh = win.h();
1962
1963        println!("\n The position of the primary window is :  ({}, {}) \n", xxx, yyy);
1964        println!("The size of the primary window is :  ({}, {}) \n", www, hhh);
1965    }
1966
1967}  // End   misc   module
1968