C++程序设计原理与实践 习题答案 第二十一章 第21章习题答案

习题要求所设计的函数类和数据图类

My_window.h

#include <iostream>
#include<random>
#include<cmath>
#include<map>
#include "../../GUI/Graph.h"        // get access to our graphics library facilities
#include "../../GUI/Gui.h"
#include "../../GUI/Window.h"
#include"../../GUI/My_Shape_V2.h"   //我把第19章创建的 My_Shape_V2 实现放到 GUI 文件夹中了

using namespace Graph_lib;

double statement(istream& is);
void read_exchange_rate(const string& fname, map<string, double> er);

//------------------------------------------------------------------------------
namespace My_windows {
    using Graph_lib::Window;
    using Graph_lib::Rectangle;

    //------------------------------------------------------------------------------
    struct My_window : Graph_lib::Window {
        My_window(Point xy, int w, int h, const string& title);

        bool wait_for_button(); // simple event loop

    protected:
        Button next_button;     // the "next" button
        Button quit_button;     // the "quit" button
        bool button_pushed;     // implementation detail

        static void cb_next(Address, Address); // callback for next_button
        static void cb_quit(Address, Address);
        virtual void next();            // action to be done when next_button is pressed
        virtual void quit();

    };



    //------------------------------------------------------------------------------
    struct Button_window : My_window {
        Button_window(Point xy, int w, int h, const string& title);
    private:
        // widgets
        Menu menu1;
        Menu menu2;
        Menu menu3;
        Menu menu4;
        Out_box xy_out;

        // actions invoked by callback: relabel button, print coordinates
        void reset_labels();    // helper function
        void action(Button& b);
        void action11() { action(menu1.selection[0]); }
        void action12() { action(menu1.selection[1]); }
        void action13() { action(menu1.selection[2]); }
        void action14() { action(menu1.selection[3]); }
        void action21() { action(menu2.selection[0]); }
        void action22() { action(menu2.selection[1]); }
        void action23() { action(menu2.selection[2]); }
        void action24() { action(menu2.selection[3]); }
        void action31() { action(menu3.selection[0]); }
        void action32() { action(menu3.selection[1]); }
        void action33() { action(menu3.selection[2]); }
        void action34() { action(menu3.selection[3]); }
        void action41() { action(menu4.selection[0]); }
        void action42() { action(menu4.selection[1]); }
        void action43() { action(menu4.selection[2]); }
        void action44() { action(menu4.selection[3]); }

        // callback functions
        static void cb_action11(Address, Address pw) { reference_to<Button_window>(pw).action11(); }
        static void cb_action12(Address, Address pw) { reference_to<Button_window>(pw).action12(); }
        static void cb_action13(Address, Address pw) { reference_to<Button_window>(pw).action13(); }
        static void cb_action14(Address, Address pw) { reference_to<Button_window>(pw).action14(); }
        static void cb_action21(Address, Address pw) { reference_to<Button_window>(pw).action21(); }
        static void cb_action22(Address, Address pw) { reference_to<Button_window>(pw).action22(); }
        static void cb_action23(Address, Address pw) { reference_to<Button_window>(pw).action23(); }
        static void cb_action24(Address, Address pw) { reference_to<Button_window>(pw).action24(); }
        static void cb_action31(Address, Address pw) { reference_to<Button_window>(pw).action31(); }
        static void cb_action32(Address, Address pw) { reference_to<Button_window>(pw).action32(); }
        static void cb_action33(Address, Address pw) { reference_to<Button_window>(pw).action33(); }
        static void cb_action34(Address, Address pw) { reference_to<Button_window>(pw).action34(); }
        static void cb_action41(Address, Address pw) { reference_to<Button_window>(pw).action41(); }
        static void cb_action42(Address, Address pw) { reference_to<Button_window>(pw).action42(); }
        static void cb_action43(Address, Address pw) { reference_to<Button_window>(pw).action43(); }
        static void cb_action44(Address, Address pw) { reference_to<Button_window>(pw).action44(); }
    };




    //------------------------------------------------------------------------------
    inline int rand_int(int min, int max)
    {
        static default_random_engine ran;
        return uniform_int_distribution<>{min, max}(ran);
    }

    struct Image_button : Button {
        Image_button(Point xy, int w, int h, const string& label,
            const string& image_fname, Callback cb);
        void move(int dx, int dy) override;
        void attach(Window&) override;

    private:
        Image img;
    };

    struct Image_button_window : My_window {
        Image_button_window(Point xy, int w, int h, const string& title, const string& fname);

    private:
        Image_button ib;

        void pos_change();
        static void cb_ib(Address, Address pw) { reference_to<Image_button_window>(pw).pos_change(); }
    };




    //------------------------------------------------------------------------------
    struct Draw_shape_window : My_window {
        Draw_shape_window(Point xy, int w, int h, const string& title);

    private:
        In_box orign_x;
        In_box orign_y;
        In_box rlen;    //radius or length of side
        Button clear_button;
        Button shape_menu_button;
        bool shape_button_pushed;
        Menu shape_menu;
    protected:
        Point current;  //当前形状的位置
        Vector_ref<Shape> rshapes;
    private:
        int i_begin;    //用于标记清屏后rshapes的起始位置

        static void cb_clear(Address, Address pw) { reference_to<Draw_shape_window>(pw).clear_pressed(); }
        static void cb_menu(Address, Address pw) { reference_to<Draw_shape_window>(pw).menu_pressed(); }
        static void cb_circle(Address, Address pw) { reference_to<Draw_shape_window>(pw).circle_pressed(); }
        static void cb_square(Address, Address pw) { reference_to<Draw_shape_window>(pw).square_pressed(); }
        static void cb_eqtri(Address, Address pw) { reference_to<Draw_shape_window>(pw).eqtri_pressed(); }
        static void cb_rhexag(Address, Address pw) { reference_to<Draw_shape_window>(pw).rhexag_pressed(); }

        //按下按钮后,回调函数调用的功能函数
        void clear_pressed();
        void menu_pressed();
        void circle_pressed();
        void square_pressed();
        void eqtri_pressed();
        void rhexag_pressed();

        void add_shape(Shape*);
        void hide_menu();

    protected:
        struct Shape_info {
            Point xy;
            int rlen;
        };
        bool get_shape_info(Shape_info& si);
    };




    //------------------------------------------------------------------------------
    struct Move_shape_window : Draw_shape_window {
        Move_shape_window(Point xy, int w, int h, const string& title);

    private:
        In_box mov_x;   //要移动到的新位置
        In_box mov_y;

        void next() override;    //覆盖next按钮原功能,设置为新的移动功能
    };




    //------------------------------------------------------------------------------
    struct Clock_dial : Shape {
        Clock_dial(Point cen, int rr);

        void draw_lines() const override;
        void move(int dx, int dy) override;

    private:
        Circle center;  //中心点
        Circle outline; //外圈表盘
        Vector_ref<Text> digital;  //外圈12个数字
        Vector_ref<Line> dial;     //外圈60个刻度
    };

    //------------------------------------------------------------------------------
    struct Clock_hands : Shape {
        Clock_hands(Point cen, int rr);

        //显示时分秒针
        void show_sechand(int s, int prev_s);
        void show_minhand(int m, int prev_m);
        void show_hourhand(int h, int prev_h);

        void draw_lines() const override;
        void move(int dx, int dy) override;

    private:
        Vector_ref<Line> sec_hand; //秒针
        Vector_ref<Line> min_hand; //分针
        Vector_ref<Line> hour_hand; //时针

        void show_hand(Vector_ref<Line>& vh, int i, int prev_i);
    };
    
    //------------------------------------------------------------------------------
    struct Clock_window : My_window {
        Clock_window(Point xy, int w, int h, const string& tt = "Simple Clock");

    private:
        Clock_dial cd;
        Clock_hands ch;
        int h;  //时
        int m;  //分
        int s;  //秒

        static void cb_run_clock(Address pw)
        {
            //这是Fl_Timeout_Handler 类型的函数,不能用this_call调用,得设置为静态函数
            reference_to<Clock_window>(pw).run_clock();
        }

        void run_clock();
    };




    //------------------------------------------------------------------------------
    struct Plane_flying_window : My_window {
        Plane_flying_window(Point xy, int w, int h, int v,
            const string& fname, const string& tt = "Flying Plane");

        enum class Direction {
            up, down, left, right
        };

    private:
        static const int img_width{ 200 };  //图像的尺寸
        static const int img_height{ 200 };
        Button start_button;    //start按钮,next按钮变为stop按钮
        Image plane;
        int v;          //速度,从1到10
        Direction dir;
        bool swt;   //标记是否开始飞行
        clock_t base;

        static void cb_start(Address, Address pw)
        {
            reference_to<Plane_flying_window>(pw).start_pressed();
        }
        
        static void cb_fly_plane(Address pw)
        {
            //这是Fl_Timeout_Handler 类型的函数,不能用this_call调用,得设置为静态函数
            reference_to<Plane_flying_window>(pw).fly_plane();
        }

        void start_pressed();
        void next() override;   //设置为 stop_pressed()
        void fly_plane();

        Direction rand_dir();
    };



    //------------------------------------------------------------------------------
    class Calculator_window : public My_window
    {
    public:
        Calculator_window(Point xy, int w, int h, const string& tt = "Calculator GUI");

    private:
        In_box stat;    //输入的声明语句
        Out_box res;    //输出结果
        //Button cal;   //用next按钮代替

        void next() override;
    };


    //------------------------------------------------------------------------------
    class Currency_exchg_rate_window : public My_window
    {
        Button curr1_button;
        Button curr2_button;
        Menu curr1_menu;
        Menu curr2_menu;
        Out_box rate1;  //第一个输出框输出基准汇率,固定为1
        Out_box rate2;
        bool is_menu1_hidden;
        bool is_menu2_hidden;
        pair<string, double> base;   //基准货币及其与美元的汇率
        pair<string, double> exchg;  //用于换算的货币
        map<string, double> exchg_rates;    //汇率映射表



        static void cb_curr1_button(Address, Address pw) { reference_to<Currency_exchg_rate_window>(pw).curr1_button_pressed(); }
        static void cb_curr2_button(Address, Address pw) { reference_to<Currency_exchg_rate_window>(pw).curr2_button_pressed(); }
        static void cb_curr1_menu(Address pb, Address pw)
        {
            //string cur_name = reference_to<Fl_Widget>(pb).label();
	        reference_to<Currency_exchg_rate_window>(pw).curr1_menu_pressed(reference_to<Fl_Widget>(pb).label());
        }
        static void cb_curr2_menu(Address pb, Address pw)
        {
            reference_to<Currency_exchg_rate_window>(pw).
        		curr2_menu_pressed(reference_to<Fl_Widget>(pb).label());
        }


        void curr1_button_pressed();
        void curr2_button_pressed();
        void curr1_menu_pressed(const string&);
        void curr2_menu_pressed(const string&);


    public:
        Currency_exchg_rate_window(Point xy, int w, int h,
            const string& fname = "exchange_rate.txt",
            const string& tt = "Currentcy Exchange Rate Window");

    };
}

My_window.cpp

#include"My_window.h"

namespace My_windows {

    //------------------------------------------------------------------------------
    My_window::My_window(Point xy, int w, int h, const string& title) :
        Window(xy, w, h, title),
        next_button(Point(x_max() - 70, 0), 70, 20, "Next", cb_next),
        quit_button(Point(x_max() - 70, 25), 70, 20, "Quit", cb_quit),
        button_pushed(false)
    {
        attach(next_button);
        attach(quit_button);
    }

    bool My_window::wait_for_button()
        // modified event loop:
        // handle all events (as per default), quit when button_pushed becomes true
        // this allows graphics without control inversion
    {
        show();
        button_pushed = false;
#if 1
        // Simpler handler
        while (!button_pushed) Fl::wait();
        Fl::redraw();
#else
        // To handle the case where the user presses the X button in the window frame
        // to kill the application, change the condition to 0 to enable this branch.
        Fl::run();
#endif
        return button_pushed;
    }

    void My_window::cb_next(Address, Address pw)
        // call Simple_window::next() for the window located at pw
    {
        reference_to<My_window>(pw).next();
    }

    void My_window::cb_quit(Address, Address pw)
        // call Simple_window::next() for the window located at pw
    {
        reference_to<My_window>(pw).quit();
    }

    void My_window::next()
    {
        button_pushed = true;
        hide();
    }

    void My_window::quit()
    {
        hide();
    }


    //------------------------------------------------------------------------------
    Button_window::Button_window(Point xy, int w, int h, const string& title)
        :My_window(xy, w, h, title),
        menu1(Point(0, 0), 100, 100, Menu::vertical, "menu1"),
        menu2(Point(100, 0), 100, 100, Menu::vertical, "menu2"),
        menu3(Point(200, 0), 100, 100, Menu::vertical, "menu3"),
        menu4(Point(300, 0), 100, 100, Menu::vertical, "menu4"),
        xy_out(Point(x_max() - 70, 60), 70, 20, "(x,y)")
    {
        menu1.attach(new Button(Point(0, 0), 0, 0, "1-1", cb_action11));
        menu1.attach(new Button(Point(0, 0), 0, 0, "1-2", cb_action12));
        menu1.attach(new Button(Point(0, 0), 0, 0, "1-3", cb_action13));
        menu1.attach(new Button(Point(0, 0), 0, 0, "1-4", cb_action14));
        attach(menu1);
        menu2.attach(new Button(Point(0, 0), 0, 0, "2-1", cb_action21));
        menu2.attach(new Button(Point(0, 0), 0, 0, "2-2", cb_action22));
        menu2.attach(new Button(Point(0, 0), 0, 0, "2-3", cb_action23));
        menu2.attach(new Button(Point(0, 0), 0, 0, "2-4", cb_action24));
        attach(menu2);
        menu3.attach(new Button(Point(0, 0), 0, 0, "3-1", cb_action31));
        menu3.attach(new Button(Point(0, 0), 0, 0, "3-2", cb_action32));
        menu3.attach(new Button(Point(0, 0), 0, 0, "3-3", cb_action33));
        menu3.attach(new Button(Point(0, 0), 0, 0, "3-4", cb_action34));
        attach(menu3);
        menu4.attach(new Button(Point(0, 0), 0, 0, "4-1", cb_action41));
        menu4.attach(new Button(Point(0, 0), 0, 0, "4-2", cb_action42));
        menu4.attach(new Button(Point(0, 0), 0, 0, "4-3", cb_action43));
        menu4.attach(new Button(Point(0, 0), 0, 0, "4-4", cb_action44));
        attach(menu4);
        attach(xy_out);
        xy_out.put("no point");
    }

    void Button_window::reset_labels()
    {
        for (int i = 0; i < 4; ++i) {
            menu1.selection[i].label = "1-" + to_string(i + 1);
            menu2.selection[i].label = "2-" + to_string(i + 1);
            menu3.selection[i].label = "3-" + to_string(i + 1);
            menu4.selection[i].label = "4-" + to_string(i + 1);
        }
        redraw();
    }

    void Button_window::action(Button& b)
    {
        reset_labels();
        b.label = "CLICKED";
        ostringstream os;
        os << '(' << b.loc.x << ',' << b.loc.y << ')';
        xy_out.put(os.str());
    }


    //------------------------------------------------------------------------------
    Image_button::Image_button(Point xy, int w, int h, const string& label,
        const string& image_fname, Callback cb)
        : Button(xy, w, h, label, cb), img{ xy,image_fname }
    {
        img.set_mask(Point{ 0,0 }, w, h);
    }

    void Image_button::move(int dx, int dy)
    {
        hide();
        pw->position(loc.x += dx, loc.y += dy);
        img.move(dx, dy);
        show();
    }

    void Image_button::attach(Window& win)
    {
        Button::attach(win);
        win.attach(img);
    }


    //------------------------------------------------------------------------------
    Image_button_window::Image_button_window(Point xy, int w, int h, 
        const string& title, const string& fname)
        :My_window(xy, w, h, title), 
        ib{ Point{x_max()/2,y_max()/2}, 70,20,"image button",fname,cb_ib}
    {
        attach(ib);
    }

    void Image_button_window::pos_change()
    {
        int rx = rand_int(0, x_max()-ib.width);
        int ry = rand_int(0, y_max()-ib.height);
        ib.move(rx - ib.loc.x, ry - ib.loc.y);
    }



    //------------------------------------------------------------------------------
    Draw_shape_window::Draw_shape_window(Point xy, int w, int h, const string& title)
        :My_window{ xy,w,h,title },
        orign_x{ Point{70,0},100,30,"Orign x:"},
        orign_y{ Point{300,0},100,30,"Orign y:"},
        rlen{ Point{600,0},100,30,"radius or side length"},
        clear_button{ Point{x_max()-150,0},70,20, "clear", cb_clear },
        shape_menu_button{ Point{10,40},100,20, "shape menu", cb_menu },
        shape_button_pushed{ false },
        shape_menu{ Point{120,40},100,30,Menu::vertical,"shape" },
        current{0,0},
        i_begin{ 0 }
    {
        attach(orign_x);
        attach(orign_y);
        attach(rlen);
        attach(clear_button);
        attach(shape_menu_button);
        shape_menu.attach(new Button{ Point{0,0},0,0,"circle",cb_circle });
        shape_menu.attach(new Button{ Point{0,0},0,0,"square",cb_square });
        shape_menu.attach(new Button{ Point{0,0},0,0,"equil-triangle",cb_eqtri });
        shape_menu.attach(new Button{ Point{0,0},0,0,"reg-hexagon",cb_rhexag });
        attach(shape_menu);
        shape_menu.hide();
    }

    void Draw_shape_window::clear_pressed()
    {
        //清屏操作,释放附加到窗口的形状
        for (int i = i_begin; i < rshapes.size(); ++i)
            detach(rshapes[i]);
        i_begin = rshapes.size();
        redraw();
        hide_menu();
    }

    void Draw_shape_window::menu_pressed()
    {
        if (!shape_button_pushed)
            shape_menu.show();
        else
            shape_menu.hide();
        shape_button_pushed = !shape_button_pushed;
    }

    void Draw_shape_window::circle_pressed()
    {
        Shape_info si;
        if (get_shape_info(si))
        {
            Circle* pc = new Circle{ si.xy,si.rlen };
            add_shape(pc);
            current = si.xy;
        }
        hide_menu();
    }

    void Draw_shape_window::square_pressed()
    {
        //这里的orign_x/y 是正方形的左上角顶点
        Shape_info si;
        if (get_shape_info(si))
        {
            Rectangle* pr = new Rectangle{ si.xy, si.rlen,si.rlen };
            add_shape(pr);
            current = si.xy;
        }
        hide_menu();
    }

    void Draw_shape_window::eqtri_pressed()
    {
        //orign_x/y 是正三角形中心点
        Shape_info si;
        if (get_shape_info(si))
        {
            //rlen 是正三角形的边长
            int r = int(round(si.rlen / sqrt(3)));
            My_shape::Regular_triangle* prt =
                new My_shape::Regular_triangle{ si.xy, r };
            add_shape(prt);
            current = si.xy;
        }
        hide_menu();
    }

    void Draw_shape_window::rhexag_pressed()
    {
        //orign_x/y 是正六边形中心点
        Shape_info si;
        if (get_shape_info(si))
        {
            //rlen 是正六边形的边长,也是中心点到顶角的距离
            My_shape::Regular_hexagon* prh =
                new My_shape::Regular_hexagon{ si.xy, si.rlen };
            add_shape(prh);
            current = si.xy;
        }
        hide_menu();
    }

    void Draw_shape_window::add_shape(Shape* ps)
    {
        ps->set_color(Color::black);
        rshapes.push_back(ps);
        attach(*ps);
    }

    void Draw_shape_window::hide_menu()
    {
        shape_menu.hide();
        shape_button_pushed = false;
    }

    bool Draw_shape_window::get_shape_info(Shape_info& si)
    {
        string s = orign_x.get_string();
        if (s == "")
            return false;
        si.xy.x = orign_x.get_int();
        s = orign_y.get_string();
        if (s == "")
            return false;
        si.xy.y = orign_y.get_int();
        s = rlen.get_string();
        if (s == "")
            return false;
        si.rlen = rlen.get_int();
        return true;
    }




    //------------------------------------------------------------------------------
    Move_shape_window::Move_shape_window(Point xy, int w, int h, const string& title)
        :Draw_shape_window{ xy,w,h,title },
        mov_x{ Point{300,40},100,30,"move x:" },
        mov_y{ Point{600,40},100,30,"move y:" }
    {
        attach(mov_x);
        attach(mov_y);
    }

    void Move_shape_window::next()
    {
        //从输入框中获取坐标对
        string s = mov_x.get_string();
        if (s == "")
            return;
        int x = mov_x.get_int();
        s = mov_y.get_string();
        if (s == "")
            return;
        int y = mov_y.get_int();

        rshapes[rshapes.size() - 1].move(x - current.x, y - current.y);
        current.x = x;
        current.y = y;
        redraw();
    }




    //------------------------------------------------------------------------------
    Clock_dial::Clock_dial(Point cen, int rr)
        :center{cen,rr/50?rr/50:2},
        outline{cen,rr}
    {
        center.set_fill_color(Color::black);
        outline.set_color(Color::black);
        outline.set_style(Line_style{ Line_style::solid,4 });
        const double half_pi = My_shape::PI / 2;    //从90度开始顺时针旋转
        const double gap = My_shape::PI / 180.0 * 6.0;  //60刻度,每个间隔6°
        double rad = -gap;   //从刻度1开始
        
        const int dlen = rr / 20 ? rr / 20 : 2; //刻度长度
        const int rdm = rr - 3 * dlen;  //数字的左下角点距离圆心的距离
        const int rsec = int(round(3.0 / 4.0 * rr));    //秒针长度
        const int rmin = int(round(2.0 / 4.0 * rr));    //分针长度
        const int rhour = int(round(1.0 / 4.0 * rr));    //时针长度
        for (int i = 1; i <= 60; ++i)
        {
            //每个刻度线两端点的位置
            int x1 = int(cen.x + round(rr * cos(half_pi + rad)));
            int y1 = int(cen.y - round(rr * sin(half_pi + rad)));
            int x2 = int(cen.x + round((rr-dlen) * cos(half_pi + rad)));
            int y2 = int(cen.y - round((rr-dlen) * sin(half_pi + rad)));
            dial.push_back(new Line{ Point{x1,y1}, Point{x2,y2} });
            dial[dial.size() - 1].set_color(Color::black);
            if (i % 5 == 0) //每隔5刻度
            {
                dial[dial.size() - 1].set_style(Line_style{ Line_style::solid,2 }); //加粗
                int n = i / 5;
                //数字中心点的位置
                int x = int(cen.x + round(rdm * cos(half_pi + rad)));
                int y = int(cen.y - round(rdm * sin(half_pi + rad)));
                digital.push_back(new Text{ Point{x - 7,y + 7},to_string(n) });
                digital[digital.size() - 1].set_color(Color::black);
                digital[digital.size() - 1].set_font(Font::times_bold);
            }
            rad -= gap; //逆时针旋转
        }
    }

    void Clock_dial::draw_lines() const
    {
        center.draw();
        outline.draw();
        for (int i = 0; i < 12; ++i)
            digital[i].draw();
        for (int i = 0; i < 60; ++i)
            dial[i].draw();
    }

    void Clock_dial::move(int dx, int dy)
    {
        center.move(dx, dy);
        outline.move(dx, dy);
        for (int i = 0; i < 12; ++i)
            digital[i].move(dx, dy);
        for (int i = 0; i < 60; ++i)
            dial[i].move(dx, dy);
    }

  


    //------------------------------------------------------------------------------
    Clock_hands::Clock_hands(Point cen, int rr)
    {
        const double half_pi = My_shape::PI / 2;    //从90度开始顺时针旋转
        const double gap = My_shape::PI / 180.0 * 6.0;  //60刻度,每个间隔6°
        double rad = 0;
        const int rsec = int(round(5.0 / 6.0 * rr));    //秒针长度
        const int rmin = int(round(4.0 / 6.0 * rr));    //分针长度
        const int rhour = int(round(3.0 / 6.0 * rr));    //时针长度
        for (int i = 0; i < 60; ++i)
        {
            //设置秒针
            int xs = int(cen.x + round(rsec * cos(half_pi + rad)));
            int ys = int(cen.y - round(rsec * sin(half_pi + rad)));
            sec_hand.push_back(new Line{ cen,Point{xs,ys} });
            //颜色默认设置为黑色,而且不可见
            sec_hand[sec_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
            
            //设置分针
            int xm = int(cen.x + round(rmin * cos(half_pi + rad)));
            int ym = int(cen.y - round(rmin * sin(half_pi + rad)));
            min_hand.push_back(new Line{ cen,Point{xm,ym} });
            min_hand[min_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
            //设置线的粗细,秒针默认,分针2,时针4
            min_hand[min_hand.size() - 1].set_style(Line_style{ Line_style::solid, 2 });
            
            //设置时针
            int xh = int(cen.x + round(rhour * cos(half_pi + rad)));
            int yh = int(cen.y - round(rhour * sin(half_pi + rad)));
            hour_hand.push_back(new Line{ cen,Point{xh,yh} });
            hour_hand[hour_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
            hour_hand[hour_hand.size() - 1].set_style(Line_style{ Line_style::solid, 4 });
            rad -= gap;
        }
    }

    void Clock_hands::show_hand(Vector_ref<Line>& vh, int i, int prev_i)
    {
        if (i == prev_i)
            return;
        //先获得之前时刻指针的颜色
        Color c = vh[prev_i].color();
        //设置i时刻单位指针的颜色
        c.set_visibility(Color::Transparency::visible);
        vh[i].set_color(c);
        //将前一时刻单位指针置为不可见
        c.set_visibility(Color::Transparency::invisible);
        vh[prev_i].set_color(c);
    }

    void Clock_hands::show_sechand(int s, int prev_s)
    {
        show_hand(sec_hand, s, prev_s);
    }

    void Clock_hands::show_minhand(int m, int prev_m)
    {
        show_hand(min_hand, m, prev_m);
    }

    void Clock_hands::show_hourhand(int h, int prev_h)
    {
        show_hand(hour_hand, h, prev_h);
    }

    void Clock_hands::draw_lines() const
    {
        for (int i = 0; i < 60; ++i)
        {
            sec_hand[i].draw();
            min_hand[i].draw();
            hour_hand[i].draw();
        }
    }

    void Clock_hands::move(int dx, int dy)
    {
        for (int i = 0; i < 60; ++i)
        {
            sec_hand[i].move(dx, dy);
            min_hand[i].move(dx, dy);
            hour_hand[i].move(dx, dy);
        }
    }



    //------------------------------------------------------------------------------
    Clock_window::Clock_window(Point xy, int ww, int hh, const string& tt)
        :My_window{ xy, ww, hh, tt},
        cd{ Point{ww / 3, hh / 3 },hh / 3 },
        ch{ Point{ww / 3, hh / 3 },hh / 3 },
        h{ 0 }, m{ 0 }, s{ 0 }
    {
        attach(cd);
        attach(ch);
        /*
        * Fl::add_timeout的作用是添加运行一次的回调函数,其函数原型如下
        * void Fl::add_timeout (double t, Fl_Timeout_Handler cb, void* argp=0); 	
        * t代表每t秒触发一次,cb是要重复执行的回调函数, argp是要传递的数据指针,我这里传递this指针。
        */
        Fl::add_timeout(0.001, cb_run_clock, this);
    }

    void Clock_window::run_clock()
    {
        //获取系统时间
        time_t t = time(nullptr);  //UNIX时间戳
        struct tm now;
        localtime_s(&now, &t);   //转变为本地时间,VS特供的安全函数

        int now_h = now.tm_hour % 12;  //12小时制
        int now_m = now.tm_min;
        int now_s = now.tm_sec;

        //小时需要特殊处理,因为只有12小时,但是指针有60个刻度
        now_h = now_h * 5 + now_m / 12;

        ch.show_hourhand(now_h, h);
        ch.show_minhand(now_m, m);
        ch.show_sechand(now_s, s);

        Fl::redraw();

        h = now_h;
        m = now_m;
        s = now_s;
        
        /*
        * Fl::repeat_timeout的作用是重复运行回调函数,其函数原型如下
        * void Fl::add_timeout (double t, Fl_Timeout_Handler cb, void* argp=0);
        * t代表每t秒触发一次,cb是要重复执行的回调函数, argp是要传递的数据指针,我这里传递this指针。
        */
        Fl::repeat_timeout(1.0, cb_run_clock, this);
    }




    //------------------------------------------------------------------------------
    Plane_flying_window::Plane_flying_window(Point xy, int w, int h, int vv,
        const string& fname, const string& tt)
        :My_window{xy,w,h,tt},
        start_button{ Point{next_button.loc.x - next_button.width - 10, next_button.loc.y},
                next_button.width, next_button.height, "start", cb_start},
        v{vv},
        plane{ Point{w / 3, h / 3}, fname},
        dir{ Direction::right },
        swt{ false },
        base{clock()}
    {
        next_button.label = "stop";
        attach(start_button);
        if (v < 1)
            v = 1;
        else if (v > 10)
            v = 10;
        plane.set_mask(Point{ 28,28 }, img_width, img_height);
        attach(plane);
    }

    void Plane_flying_window::start_pressed()
    {
        swt = true;
        Fl::add_timeout(0, cb_fly_plane, this);
    }

    void Plane_flying_window::next()
    {
        Fl::remove_timeout(cb_fly_plane);
        swt = false;
    }

    void Plane_flying_window::fly_plane()
    {
        if ((clock() - base) / CLOCKS_PER_SEC >= 1)
        {
            //每隔 1 秒换方向
            Direction old_dir = dir;
            while ((dir = rand_dir()) == old_dir)
                continue;
            base = clock();
        }

        if (swt)
        {
            switch (dir)
            {
            case Direction::up:
                if (plane.point(0).y <= 0)
                {
                    dir = rand_dir();
                }
                else
                    plane.move(0, -v);
                break;
            case Direction::down:
                if (plane.point(0).y >= y_max() - img_height)
                {
                    dir = rand_dir();
                }
                else
                    plane.move(0, v);
                break;
            case Direction::left:
                if (plane.point(0).x <= 0)
                {
                    dir = rand_dir();
                }
                else
                    plane.move(-v, 0);
                break;
            case Direction::right:
                if (plane.point(0).x >= x_max() - img_width)
                {
                    dir = rand_dir();
                }
                else
                    plane.move(v, 0);
                break;
            default:
                error("Wrong direction");
            }
            redraw();
            //Sleep(10);
        }

        Fl::repeat_timeout(1.0/100, cb_fly_plane, this);
    }

    Plane_flying_window::Direction Plane_flying_window::rand_dir()
    {
        return Direction(rand_int(0,3));
    }




    //------------------------------------------------------------------------------
    Calculator_window::Calculator_window(Point xy, int w, int h, const string& tt)
        :My_window(xy, w, h, tt),
		stat{Point{xy.x + 50, xy.y + 50}, 200, 20, "statement"},
        res{Point{xy.x + 50, xy.y + 80}, 200, 20, "result"}
        //cal{ Point{xy.x + 170, xy.y + 50}, 50, 20, "calculate", }
	{
        attach(stat);
        attach(res);
        //修改next按钮的位置和名称
        Point next_loc = next_button.loc;
        int dx = stat.loc.x + stat.width + 20 - next_loc.x;
        int dy = stat.loc.y - next_loc.y;
        next_button.move(dx, dy);
        next_button.label = "calculate";
    }

    void Calculator_window::next()
    {
        istringstream iss{ stat.get_string() };
        ostringstream oss;
    	try {
            double result = statement(iss);
            oss << result;
        }
        catch (runtime_error& e) {
            oss << e.what();
        }
        res.put(oss.str());

    }




    //------------------------------------------------------------------------------
    static void skip_line(istream& is)
    {
	    //功能:跳过该行,即读入并忽略改行剩余字符
        char ch{ 0 };
        while (is.get(ch) && ch != '\n')
            continue;

    }
    void read_exchange_rate(const string& fname, map<string, double>& er)
    {
        //功能:从汇率文件读入汇率到映射表中,如果遇到文件中的注释则跳过改行,
        //注释是以'#'开始的一行
        ifstream ifs{ fname };
        if (!ifs)
            error("Can't open ", fname);
        char ch{ 0 };
        while(ifs >> ch)
        {
            if (ch == '#')
                skip_line(ifs);
            else
            {
                ifs.putback(ch);
                string cur;
                double rate{ -1.0 };
                ifs >> cur >> rate;
                if (cur == "")
                    error("Read currency name failed");
                else if (rate <= 0) //确保不读入0或者负数
                    error("Invalid rate: non-positive");
                else
                    er[cur] = rate;
            }
        }
    }

    Currency_exchg_rate_window::Currency_exchg_rate_window(Point xy, int w, int h,
                                                           const string& fname, const string& tt)
	    :My_window(xy, w, h, tt),
		curr1_button(Point{x_max()/2-150, y_max()/3},100,30,"Currency 1", cb_curr1_button),
		curr2_button(Point{x_max()/2+50, y_max()/3},100,30,"Currency 2", cb_curr2_button),
        curr1_menu(Point{ curr1_button.loc.x,curr1_button.loc.y + curr1_button.height }, curr1_button.width, curr1_button.height, Menu::vertical, "Currency 1 menu"),
		curr2_menu(Point{ curr2_button.loc.x,curr2_button.loc.y + curr2_button.height }, curr2_button.width,curr2_button.height,Menu::vertical,"Currency 2 menu"),
		rate1(Point{curr1_button.loc.x, curr1_button.loc.y + curr1_button.height + 30},curr1_button.width,curr1_button.height,""),
		rate2(Point{curr2_button.loc.x, curr2_button.loc.y + curr2_button.height + 30},curr2_button.width,curr2_button.height,""),
        is_menu1_hidden{true}, is_menu2_hidden{true},
		base{"",0.0}, exchg{"", 0.0}
    {
        read_exchange_rate(fname, exchg_rates);
        next_button.hide(); //不需要next按钮
        attach(curr1_button);
        attach(curr2_button);
        attach(rate1);
        attach(rate2);
        rate1.put("1");
    	//接下来根据读入的汇率创建货币菜单
        for (const auto& p : exchg_rates)
        {
            curr1_menu.attach(new Button{ Point{0,0},0,0,p.first,cb_curr1_menu });
            curr2_menu.attach(new Button{ Point{0,0},0,0,p.first,cb_curr2_menu });
        }
        attach(curr1_menu);
        attach(curr2_menu);
        curr1_menu.hide();
        curr2_menu.hide();
    }

    void Currency_exchg_rate_window::curr1_button_pressed()
    {
        if (is_menu1_hidden)
            curr1_menu.show();
        else
            curr1_menu.hide();
        is_menu1_hidden = !is_menu1_hidden;

        //还会影响菜单2
        if (!is_menu2_hidden)
        {
            curr2_menu.hide();
            is_menu2_hidden = true;
        }
    }

    void Currency_exchg_rate_window::curr2_button_pressed()
    {
        if (is_menu2_hidden)
            curr2_menu.show();
        else
            curr2_menu.hide();
        is_menu2_hidden = !is_menu2_hidden;

        //还会影响菜单1
        if (!is_menu1_hidden)
        {
            curr1_menu.hide();
            is_menu1_hidden = true;
        }
    }

    void Currency_exchg_rate_window::curr1_menu_pressed(const string& curr_name)
    {
        //设置基准货币,隐藏菜单,显示带有当前货币名的按钮,计算并输出新的汇率
        base.first = curr_name;
        base.second = exchg_rates[curr_name];

        curr1_menu.hide();
        is_menu1_hidden = true;

        curr1_button.label = curr_name;

        if(exchg.second > 0.0)
        {
            double res = exchg.second / base.second;
            ostringstream oss;
            oss << res;
            rate2.put(oss.str());
        }

    }

    void Currency_exchg_rate_window::curr2_menu_pressed(const string& curr_name)
    {
        //设置基准货币,隐藏菜单,显示带有当前货币名的按钮,计算并输出新的汇率
        exchg.first = curr_name;
        exchg.second = exchg_rates[curr_name];

        curr2_menu.hide();
        is_menu2_hidden = true;

        curr2_button.label = curr_name;

        if (base.second > 0.0)
        {
            double res = exchg.second / base.second;
            ostringstream oss;
            oss << res;
            rate2.put(oss.str());
        }
    }

}

main.cpp

#include"My_window.h"

using namespace My_windows;


int main()
try {
    Point tt{ 100,100 };
    
    /*My_window mywin{ tt, 1200,800,"My Window" };
    
    Line ll{ Point{100,100},Point{200,200} };
    ll.set_color(Color::black);
    mywin.attach(ll);

    Graph_lib::Rectangle rr{ Point{300,100},Point{600,200} };
    rr.set_color(Color::blue);
    mywin.attach(rr);*/

    //Button_window bwin{ tt,1200,800,"Button Window" };

    //Image_button_window ibwin{ tt,1200,800,"Image Button Window","snow_cpp.gif" };

    //Draw_shape_window dswin{ tt,1200,800,"Draw Shape Window" };
    //Move_shape_window mvwin{ tt,1200,800,"Move Shape Window" };

    //Clock_window cwin{ tt, 1200, 800 };

	//Plane_flying_window pfwin{ tt, 1200, 800, 5, "plane.jpg" };

    //Currency_exchg_rate_window cerwin{ tt,1200,800 };

    Calculator_window calwin{ tt, 1200,800 };

    return gui_main();
}
catch (exception& e) {
    cerr << "exception: " << e.what() << '\n';
    return 1;
}
catch (...) {
    cerr << "Some exception\n";
    return 2;
}

21.1

struct My_window : Graph_lib::Window {
    My_window(Point xy, int w, int h, const string& title);

    bool wait_for_button(); // simple event loop

protected:
    Button next_button;     // the "next" button
    Button quit_button;     // the "quit" button
    bool button_pushed;     // implementation detail

    static void cb_next(Address, Address); // callback for next_button
    static void cb_quit(Address, Address);
    virtual void next();            // action to be done when next_button is pressed
    virtual void quit();

};

//------------------------------------------------------------------------------
My_window::My_window(Point xy, int w, int h, const string& title) :
    Window(xy, w, h, title),
    next_button(Point(x_max() - 70, 0), 70, 20, "Next", cb_next),
    quit_button(Point(x_max() - 70, 25), 70, 20, "Quit", cb_quit),
    button_pushed(false)
{
    attach(next_button);
    attach(quit_button);
}

bool My_window::wait_for_button()
    // modified event loop:
    // handle all events (as per default), quit when button_pushed becomes true
    // this allows graphics without control inversion
{
    show();
    button_pushed = false;
#if 1
    // Simpler handler
    while (!button_pushed) Fl::wait();
    Fl::redraw();
#else
    // To handle the case where the user presses the X button in the window frame
    // to kill the application, change the condition to 0 to enable this branch.
    Fl::run();
#endif
    return button_pushed;
}

void My_window::cb_next(Address, Address pw)
    // call Simple_window::next() for the window located at pw
{
    reference_to<My_window>(pw).next();
}

void My_window::cb_quit(Address, Address pw)
    // call Simple_window::next() for the window located at pw
{
    reference_to<My_window>(pw).quit();
}

void My_window::next()
{
    button_pushed = true;
    hide();
}

void My_window::quit()
{
    hide();
}

21.2

struct Button_window : My_window {
    Button_window(Point xy, int w, int h, const string& title);
private:
    // widgets
    Menu menu1;
    Menu menu2;
    Menu menu3;
    Menu menu4;
    Out_box xy_out;

    // actions invoked by callback: relabel button, print coordinates
    void reset_labels();    // helper function
    void action(Button& b);
    void action11() { action(menu1.selection[0]); }
    void action12() { action(menu1.selection[1]); }
    void action13() { action(menu1.selection[2]); }
    void action14() { action(menu1.selection[3]); }
    void action21() { action(menu2.selection[0]); }
    void action22() { action(menu2.selection[1]); }
    void action23() { action(menu2.selection[2]); }
    void action24() { action(menu2.selection[3]); }
    void action31() { action(menu3.selection[0]); }
    void action32() { action(menu3.selection[1]); }
    void action33() { action(menu3.selection[2]); }
    void action34() { action(menu3.selection[3]); }
    void action41() { action(menu4.selection[0]); }
    void action42() { action(menu4.selection[1]); }
    void action43() { action(menu4.selection[2]); }
    void action44() { action(menu4.selection[3]); }

    // callback functions
    static void cb_action11(Address, Address pw) { reference_to<Button_window>(pw).action11(); }
    static void cb_action12(Address, Address pw) { reference_to<Button_window>(pw).action12(); }
    static void cb_action13(Address, Address pw) { reference_to<Button_window>(pw).action13(); }
    static void cb_action14(Address, Address pw) { reference_to<Button_window>(pw).action14(); }
    static void cb_action21(Address, Address pw) { reference_to<Button_window>(pw).action21(); }
    static void cb_action22(Address, Address pw) { reference_to<Button_window>(pw).action22(); }
    static void cb_action23(Address, Address pw) { reference_to<Button_window>(pw).action23(); }
    static void cb_action24(Address, Address pw) { reference_to<Button_window>(pw).action24(); }
    static void cb_action31(Address, Address pw) { reference_to<Button_window>(pw).action31(); }
    static void cb_action32(Address, Address pw) { reference_to<Button_window>(pw).action32(); }
    static void cb_action33(Address, Address pw) { reference_to<Button_window>(pw).action33(); }
    static void cb_action34(Address, Address pw) { reference_to<Button_window>(pw).action34(); }
    static void cb_action41(Address, Address pw) { reference_to<Button_window>(pw).action41(); }
    static void cb_action42(Address, Address pw) { reference_to<Button_window>(pw).action42(); }
    static void cb_action43(Address, Address pw) { reference_to<Button_window>(pw).action43(); }
    static void cb_action44(Address, Address pw) { reference_to<Button_window>(pw).action44(); }
};

//------------------------------------------------------------------------------
Button_window::Button_window(Point xy, int w, int h, const string& title)
    :My_window(xy, w, h, title),
    menu1(Point(0, 0), 100, 100, Menu::vertical, "menu1"),
    menu2(Point(100, 0), 100, 100, Menu::vertical, "menu2"),
    menu3(Point(200, 0), 100, 100, Menu::vertical, "menu3"),
    menu4(Point(300, 0), 100, 100, Menu::vertical, "menu4"),
    xy_out(Point(x_max() - 70, 60), 70, 20, "(x,y)")
{
    menu1.attach(new Button(Point(0, 0), 0, 0, "1-1", cb_action11));
    menu1.attach(new Button(Point(0, 0), 0, 0, "1-2", cb_action12));
    menu1.attach(new Button(Point(0, 0), 0, 0, "1-3", cb_action13));
    menu1.attach(new Button(Point(0, 0), 0, 0, "1-4", cb_action14));
    attach(menu1);
    menu2.attach(new Button(Point(0, 0), 0, 0, "2-1", cb_action21));
    menu2.attach(new Button(Point(0, 0), 0, 0, "2-2", cb_action22));
    menu2.attach(new Button(Point(0, 0), 0, 0, "2-3", cb_action23));
    menu2.attach(new Button(Point(0, 0), 0, 0, "2-4", cb_action24));
    attach(menu2);
    menu3.attach(new Button(Point(0, 0), 0, 0, "3-1", cb_action31));
    menu3.attach(new Button(Point(0, 0), 0, 0, "3-2", cb_action32));
    menu3.attach(new Button(Point(0, 0), 0, 0, "3-3", cb_action33));
    menu3.attach(new Button(Point(0, 0), 0, 0, "3-4", cb_action34));
    attach(menu3);
    menu4.attach(new Button(Point(0, 0), 0, 0, "4-1", cb_action41));
    menu4.attach(new Button(Point(0, 0), 0, 0, "4-2", cb_action42));
    menu4.attach(new Button(Point(0, 0), 0, 0, "4-3", cb_action43));
    menu4.attach(new Button(Point(0, 0), 0, 0, "4-4", cb_action44));
    attach(menu4);
    attach(xy_out);
    xy_out.put("no point");
}

void Button_window::reset_labels()
{
    for (int i = 0; i < 4; ++i) {
        menu1.selection[i].label = "1-" + to_string(i + 1);
        menu2.selection[i].label = "2-" + to_string(i + 1);
        menu3.selection[i].label = "3-" + to_string(i + 1);
        menu4.selection[i].label = "4-" + to_string(i + 1);
    }
    redraw();
}

void Button_window::action(Button& b)
{
    reset_labels();
    b.label = "CLICKED";
    ostringstream os;
    os << '(' << b.loc.x << ',' << b.loc.y << ')';
    xy_out.put(os.str());
}

21.3

inline int rand_int(int min, int max)
{
    static default_random_engine ran;
    return uniform_int_distribution<>{min, max}(ran);
}

struct Image_button : Button {
    Image_button(Point xy, int w, int h, const string& label,
        const string& image_fname, Callback cb);
    void move(int dx, int dy) override;
    void attach(Window&) override;

private:
    Image img;
};

struct Image_button_window : My_window {
    Image_button_window(Point xy, int w, int h, const string& title, const string& fname);

private:
    Image_button ib;

    void pos_change();
    static void cb_ib(Address, Address pw) { reference_to<Image_button_window>(pw).pos_change(); }
};

//------------------------------------------------------------------------------
Image_button::Image_button(Point xy, int w, int h, const string& label,
    const string& image_fname, Callback cb)
    : Button(xy, w, h, label, cb), img{ xy,image_fname }
{
    img.set_mask(Point{ 0,0 }, w, h);
}

void Image_button::move(int dx, int dy)
{
    hide();
    pw->position(loc.x += dx, loc.y += dy);
    img.move(dx, dy);
    show();
}

void Image_button::attach(Window& win)
{
    Button::attach(win);
    win.attach(img);
}

//------------------------------------------------------------------------------
Image_button_window::Image_button_window(Point xy, int w, int h, 
    const string& title, const string& fname)
    :My_window(xy, w, h, title), 
    ib{ Point{x_max()/2,y_max()/2}, 70,20,"image button",fname,cb_ib}
{
    attach(ib);
}

void Image_button_window::pos_change()
{
    int rx = rand_int(0, x_max()-ib.width);
    int ry = rand_int(0, y_max()-ib.height);
    ib.move(rx - ib.loc.x, ry - ib.loc.y);
}

21.4

struct Draw_shape_window : My_window {
    Draw_shape_window(Point xy, int w, int h, const string& title);

private:
    In_box orign_x;
    In_box orign_y;
    In_box rlen;    //radius or length of side
    Button clear_button;
    Button shape_menu_button;
    bool shape_button_pushed;
    Menu shape_menu;
protected:
    Point current;  //当前形状的位置
    Vector_ref<Shape> rshapes;
private:
    int i_begin;    //用于标记清屏后rshapes的起始位置

    static void cb_clear(Address, Address pw) { reference_to<Draw_shape_window>(pw).clear_pressed(); }
    static void cb_menu(Address, Address pw) { reference_to<Draw_shape_window>(pw).menu_pressed(); }
    static void cb_circle(Address, Address pw) { reference_to<Draw_shape_window>(pw).circle_pressed(); }
    static void cb_square(Address, Address pw) { reference_to<Draw_shape_window>(pw).square_pressed(); }
    static void cb_eqtri(Address, Address pw) { reference_to<Draw_shape_window>(pw).eqtri_pressed(); }
    static void cb_rhexag(Address, Address pw) { reference_to<Draw_shape_window>(pw).rhexag_pressed(); }

    void clear_pressed();
    void menu_pressed();
    void circle_pressed();
    void square_pressed();
    void eqtri_pressed();
    void rhexag_pressed();

    void add_shape(Shape*);
    void hide_menu();

protected:
    struct Shape_info {
        Point xy;
        int rlen;
    };
    bool get_shape_info(Shape_info& si);
};

//------------------------------------------------------------------------------
Draw_shape_window::Draw_shape_window(Point xy, int w, int h, const string& title)
    :My_window{ xy,w,h,title },
    orign_x{ Point{70,0},100,30,"Orign x:"},
    orign_y{ Point{300,0},100,30,"Orign y:"},
    rlen{ Point{600,0},100,30,"radius or side length"},
    clear_button{ Point{x_max()-150,0},70,20, "clear", cb_clear },
    shape_menu_button{ Point{10,40},100,20, "shape menu", cb_menu },
    shape_button_pushed{ false },
    shape_menu{ Point{120,40},100,30,Menu::vertical,"shape" },
    current{0,0},
    i_begin{ 0 }
{
    attach(orign_x);
    attach(orign_y);
    attach(rlen);
    attach(clear_button);
    attach(shape_menu_button);
    shape_menu.attach(new Button{ Point{0,0},0,0,"circle",cb_circle });
    shape_menu.attach(new Button{ Point{0,0},0,0,"square",cb_square });
    shape_menu.attach(new Button{ Point{0,0},0,0,"equil-triangle",cb_eqtri });
    shape_menu.attach(new Button{ Point{0,0},0,0,"reg-hexagon",cb_rhexag });
    attach(shape_menu);
    shape_menu.hide();
}

void Draw_shape_window::clear_pressed()
{
    //清屏操作,释放附加到窗口的形状
    for (int i = i_begin; i < rshapes.size(); ++i)
        detach(rshapes[i]);
    i_begin = rshapes.size();
    redraw();
    hide_menu();
}

void Draw_shape_window::menu_pressed()
{
    if (!shape_button_pushed)
        shape_menu.show();
    else
        shape_menu.hide();
    shape_button_pushed = !shape_button_pushed;
}

void Draw_shape_window::circle_pressed()
{
    Shape_info si;
    if (get_shape_info(si))
    {
        Circle* pc = new Circle{ si.xy,si.rlen };
        add_shape(pc);
        current = si.xy;
    }
    hide_menu();
}

void Draw_shape_window::square_pressed()
{
    //这里的orign_x/y 是正方形的左上角顶点
    Shape_info si;
    if (get_shape_info(si))
    {
        Rectangle* pr = new Rectangle{ si.xy, si.rlen,si.rlen };
        add_shape(pr);
        current = si.xy;
    }
    hide_menu();
}

void Draw_shape_window::eqtri_pressed()
{
    //orign_x/y 是正三角形中心点
    Shape_info si;
    if (get_shape_info(si))
    {
        //rlen 是正三角形的边长
        int r = int(round(si.rlen / sqrt(3)));
        My_shape::Regular_triangle* prt =
            new My_shape::Regular_triangle{ si.xy, r };
        add_shape(prt);
        current = si.xy;
    }
    hide_menu();
}

void Draw_shape_window::rhexag_pressed()
{
    //orign_x/y 是正六边形中心点
    Shape_info si;
    if (get_shape_info(si))
    {
        //rlen 是正六边形的边长,也是中心点到顶角的距离
        My_shape::Regular_hexagon* prh =
            new My_shape::Regular_hexagon{ si.xy, si.rlen };
        add_shape(prh);
        current = si.xy;
    }
    hide_menu();
}

void Draw_shape_window::add_shape(Shape* ps)
{
    ps->set_color(Color::black);
    rshapes.push_back(ps);
    attach(*ps);
}

void Draw_shape_window::hide_menu()
{
    shape_menu.hide();
    shape_button_pushed = false;
}

bool Draw_shape_window::get_shape_info(Shape_info& si)
{
    string s = orign_x.get_string();
    if (s == "")
        return false;
    si.xy.x = orign_x.get_int();
    s = orign_y.get_string();
    if (s == "")
        return false;
    si.xy.y = orign_y.get_int();
    s = rlen.get_string();
    if (s == "")
        return false;
    si.rlen = rlen.get_int();
    return true;
}

21.5

struct Move_shape_window : Draw_shape_window {
    Move_shape_window(Point xy, int w, int h, const string& title);

private:
    In_box mov_x;   //要移动到的新位置
    In_box mov_y;

    void next() override;    //覆盖next按钮原功能,设置为新的移动功能
};

//------------------------------------------------------------------------------
Move_shape_window::Move_shape_window(Point xy, int w, int h, const string& title)
    :Draw_shape_window{ xy,w,h,title },
    mov_x{ Point{300,40},100,30,"move x:" },
    mov_y{ Point{600,40},100,30,"move y:" }
{
    attach(mov_x);
    attach(mov_y);
}

void Move_shape_window::next()
{
    //从输入框中获取坐标对
    string s = mov_x.get_string();
    if (s == "")
        return;
    int x = mov_x.get_int();
    s = mov_y.get_string();
    if (s == "")
        return;
    int y = mov_y.get_int();

    rshapes[rshapes.size() - 1].move(x - current.x, y - current.y);
    current.x = x;
    current.y = y;
    redraw();
}

21.6 Clock

struct Clock_dial : Shape {
    Clock_dial(Point cen, int rr);
    void draw_lines() const override;
    void move(int dx, int dy) override;
private:
    Circle center;  //中心点
    Circle outline; //外圈表盘
    Vector_ref<Text> digital;  //外圈12个数字
    Vector_ref<Line> dial;     //外圈60个刻度
};
//------------------------------------------------------------------------------
struct Clock_hands : Shape {
    Clock_hands(Point cen, int rr);
    //显示时分秒针
    void show_sechand(int s, int prev_s);
    void show_minhand(int m, int prev_m);
    void show_hourhand(int h, int prev_h);

    void draw_lines() const override;
    void move(int dx, int dy) override;
private:
    Vector_ref<Line> sec_hand; //秒针
    Vector_ref<Line> min_hand; //分针
    Vector_ref<Line> hour_hand; //时针
    void show_hand(Vector_ref<Line>& vh, int i, int prev_i);
};
//------------------------------------------------------------------------------
struct Clock_window : My_window {
    Clock_window(Point xy, int w, int h, const string& tt = "Simple Clock");
private:
    Clock_dial cd;
    Clock_hands ch;
    int h;  //时
    int m;  //分
    int s;  //秒
    static void cb_run_clock(Address pw)
    {
        //这是Fl_Timeout_Handler 类型的函数,不能用this_call调用,得设置为静态函数
        reference_to<Clock_window>(pw).run_clock();
    }
    void run_clock();
};

//------------------------------------------------------------------------------
Clock_dial::Clock_dial(Point cen, int rr)
    :center{cen,rr/50?rr/50:2},
    outline{cen,rr}
{
    center.set_fill_color(Color::black);
    outline.set_color(Color::black);
    outline.set_style(Line_style{ Line_style::solid,4 });
    const double half_pi = My_shape::PI / 2;    //从90度开始顺时针旋转
    const double gap = My_shape::PI / 180.0 * 6.0;  //60刻度,每个间隔6°
    double rad = -gap;   //从刻度1开始
    
    const int dlen = rr / 20 ? rr / 20 : 2; //刻度长度
    const int rdm = rr - 3 * dlen;  //数字的左下角点距离圆心的距离
    const int rsec = int(round(3.0 / 4.0 * rr));    //秒针长度
    const int rmin = int(round(2.0 / 4.0 * rr));    //分针长度
    const int rhour = int(round(1.0 / 4.0 * rr));    //时针长度
    for (int i = 1; i <= 60; ++i)
    {
        //每个刻度线两端点的位置
        int x1 = int(cen.x + round(rr * cos(half_pi + rad)));
        int y1 = int(cen.y - round(rr * sin(half_pi + rad)));
        int x2 = int(cen.x + round((rr-dlen) * cos(half_pi + rad)));
        int y2 = int(cen.y - round((rr-dlen) * sin(half_pi + rad)));
        dial.push_back(new Line{ Point{x1,y1}, Point{x2,y2} });
        dial[dial.size() - 1].set_color(Color::black);
        if (i % 5 == 0) //每隔5刻度
        {
            dial[dial.size() - 1].set_style(Line_style{ Line_style::solid,2 }); //加粗
            int n = i / 5;
            //数字中心点的位置
            int x = int(cen.x + round(rdm * cos(half_pi + rad)));
            int y = int(cen.y - round(rdm * sin(half_pi + rad)));
            digital.push_back(new Text{ Point{x - 7,y + 7},to_string(n) });
            digital[digital.size() - 1].set_color(Color::black);
            digital[digital.size() - 1].set_font(Font::times_bold);
        }
        rad -= gap; //逆时针旋转
    }
}

void Clock_dial::draw_lines() const
{
    center.draw();
    outline.draw();
    for (int i = 0; i < 12; ++i)
        digital[i].draw();
    for (int i = 0; i < 60; ++i)
        dial[i].draw();
}

void Clock_dial::move(int dx, int dy)
{
    center.move(dx, dy);
    outline.move(dx, dy);
    for (int i = 0; i < 12; ++i)
        digital[i].move(dx, dy);
    for (int i = 0; i < 60; ++i)
        dial[i].move(dx, dy);
}


//------------------------------------------------------------------------------
Clock_hands::Clock_hands(Point cen, int rr)
{
    const double half_pi = My_shape::PI / 2;    //从90度开始顺时针旋转
    const double gap = My_shape::PI / 180.0 * 6.0;  //60刻度,每个间隔6°
    double rad = 0;
    const int rsec = int(round(5.0 / 6.0 * rr));    //秒针长度
    const int rmin = int(round(4.0 / 6.0 * rr));    //分针长度
    const int rhour = int(round(3.0 / 6.0 * rr));    //时针长度
    for (int i = 0; i < 60; ++i)
    {
        //设置秒针
        int xs = int(cen.x + round(rsec * cos(half_pi + rad)));
        int ys = int(cen.y - round(rsec * sin(half_pi + rad)));
        sec_hand.push_back(new Line{ cen,Point{xs,ys} });
        //颜色默认设置为黑色,而且不可见
        sec_hand[sec_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
        
        //设置分针
        int xm = int(cen.x + round(rmin * cos(half_pi + rad)));
        int ym = int(cen.y - round(rmin * sin(half_pi + rad)));
        min_hand.push_back(new Line{ cen,Point{xm,ym} });
        min_hand[min_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
        //设置线的粗细,秒针默认,分针2,时针4
        min_hand[min_hand.size() - 1].set_style(Line_style{ Line_style::solid, 2 });
        
        //设置时针
        int xh = int(cen.x + round(rhour * cos(half_pi + rad)));
        int yh = int(cen.y - round(rhour * sin(half_pi + rad)));
        hour_hand.push_back(new Line{ cen,Point{xh,yh} });
        hour_hand[hour_hand.size() - 1].set_color(Color{ Color::black,Color::invisible });
        hour_hand[hour_hand.size() - 1].set_style(Line_style{ Line_style::solid, 4 });
        rad -= gap;
    }
}

void Clock_hands::show_hand(Vector_ref<Line>& vh, int i, int prev_i)
{
    if (i == prev_i)
        return;
    //先获得之前时刻指针的颜色
    Color c = vh[prev_i].color();
    //设置i时刻单位指针的颜色
    c.set_visibility(Color::Transparency::visible);
    vh[i].set_color(c);
    //将前一时刻单位指针置为不可见
    c.set_visibility(Color::Transparency::invisible);
    vh[prev_i].set_color(c);
}

void Clock_hands::show_sechand(int s, int prev_s)
{
    show_hand(sec_hand, s, prev_s);
}

void Clock_hands::show_minhand(int m, int prev_m)
{
    show_hand(min_hand, m, prev_m);
}

void Clock_hands::show_hourhand(int h, int prev_h)
{
    show_hand(hour_hand, h, prev_h);
}

void Clock_hands::draw_lines() const
{
    for (int i = 0; i < 60; ++i)
    {
        sec_hand[i].draw();
        min_hand[i].draw();
        hour_hand[i].draw();
    }
}

void Clock_hands::move(int dx, int dy)
{
    for (int i = 0; i < 60; ++i)
    {
        sec_hand[i].move(dx, dy);
        min_hand[i].move(dx, dy);
        hour_hand[i].move(dx, dy);
    }
}


//------------------------------------------------------------------------------
Clock_window::Clock_window(Point xy, int ww, int hh, const string& tt)
    :My_window{ xy, ww, hh, tt},
    cd{ Point{ww / 3, hh / 3 },hh / 3 },
    ch{ Point{ww / 3, hh / 3 },hh / 3 },
    h{ 0 }, m{ 0 }, s{ 0 }
{
    attach(cd);
    attach(ch);
    /*
    * Fl::add_timeout的作用是添加运行一次的回调函数,其函数原型如下
    * void Fl::add_timeout (double t, Fl_Timeout_Handler cb, void* argp=0); 	
    * t代表每t秒触发一次,cb是要重复执行的回调函数, argp是要传递的数据指针,我这里传递this指针。
    */
    Fl::add_timeout(0.001, cb_run_clock, this);
}

void Clock_window::run_clock()
{
    //获取系统时间
    time_t t = time(nullptr);  //UNIX时间戳
    struct tm now;
    localtime_s(&now, &t);   //转变为本地时间,VS特供的安全函数

    int now_h = now.tm_hour % 12;  //12小时制
    int now_m = now.tm_min;
    int now_s = now.tm_sec;
    //小时需要特殊处理,因为只有12小时,但是指针有60个刻度
    now_h = now_h * 5 + now_m / 12;

    ch.show_hourhand(now_h, h);
    ch.show_minhand(now_m, m);
    ch.show_sechand(now_s, s);

    Fl::redraw();

    h = now_h;
    m = now_m;
    s = now_s;
    /*
    * Fl::repeat_timeout的作用是重复运行回调函数,其函数原型如下
    * void Fl::add_timeout (double t, Fl_Timeout_Handler cb, void* argp=0);
    * t代表每t秒触发一次,cb是要重复执行的回调函数, argp是要传递的数据指针,我这里传递this指针。
    */
    Fl::repeat_timeout(1.0, cb_run_clock, this);
}

21.7 Plane

struct Plane_flying_window : My_window {
    Plane_flying_window(Point xy, int w, int h, int v,
        const string& fname, const string& tt = "Flying Plane");

    enum class Direction {
        up, down, left, right
    };

private:
    static const int img_width{ 200 };  //图像的尺寸
    static const int img_height{ 200 };
    Button start_button;    //start按钮,next按钮变为stop按钮
    Image plane;
    int v;          //速度,从1到10
    Direction dir;
    bool swt;   //标记是否开始飞行
    clock_t base;

    static void cb_start(Address, Address pw)
    {
        reference_to<Plane_flying_window>(pw).start_pressed();
    }
    
    static void cb_fly_plane(Address pw)
    {
        //这是Fl_Timeout_Handler 类型的函数,不能用this_call调用,得设置为静态函数
        reference_to<Plane_flying_window>(pw).fly_plane();
    }

    void start_pressed();
    void next() override;   //设置为 stop_pressed()
    void fly_plane();

    Direction rand_dir();
};

//------------------------------------------------------------------------------
Plane_flying_window::Plane_flying_window(Point xy, int w, int h, int vv,
    const string& fname, const string& tt)
    :My_window{xy,w,h,tt},
    start_button{ Point{next_button.loc.x - next_button.width - 10, next_button.loc.y},
            next_button.width, next_button.height, "start", cb_start},
    v{vv},
    plane{ Point{w / 3, h / 3}, fname},
    dir{ Direction::right },
    swt{ false },
    base{clock()}
{
    next_button.label = "stop";
    attach(start_button);
    if (v < 1)
        v = 1;
    else if (v > 10)
        v = 10;
    plane.set_mask(Point{ 28,28 }, img_width, img_height);
    attach(plane);
}

void Plane_flying_window::start_pressed()
{
    swt = true;
    Fl::add_timeout(0, cb_fly_plane, this);
}

void Plane_flying_window::next()
{
    Fl::remove_timeout(cb_fly_plane);
    swt = false;
}

void Plane_flying_window::fly_plane()
{
    if ((clock() - base) / CLOCKS_PER_SEC >= 1)
    {
        //每隔 1 秒换方向
        Direction old_dir = dir;
        while ((dir = rand_dir()) == old_dir)
            continue;
        base = clock();
    }

    if (swt)
    {
        switch (dir)
        {
        case Direction::up:
            if (plane.point(0).y <= 0)
            {
                dir = rand_dir();
            }
            else
                plane.move(0, -v);
            break;
        case Direction::down:
            if (plane.point(0).y >= y_max() - img_height)
            {
                dir = rand_dir();
            }
            else
                plane.move(0, v);
            break;
        case Direction::left:
            if (plane.point(0).x <= 0)
            {
                dir = rand_dir();
            }
            else
                plane.move(-v, 0);
            break;
        case Direction::right:
            if (plane.point(0).x >= x_max() - img_width)
            {
                dir = rand_dir();
            }
            else
                plane.move(v, 0);
            break;
        default:
            error("Wrong direction");
        }
        redraw();
        //Sleep(10);
    }

    Fl::repeat_timeout(1.0/100, cb_fly_plane, this);
}

Plane_flying_window::Direction Plane_flying_window::rand_dir()
{
    return Direction(rand_int(0,3));
}

21.8 Currency Exchange Rate

class Currency_exchg_rate_window : public My_window
{
    Button curr1_button;
    Button curr2_button;
    Menu curr1_menu;
    Menu curr2_menu;
    Out_box rate1;  //第一个输出框输出基准汇率,固定为1
    Out_box rate2;
    bool is_menu1_hidden;
    bool is_menu2_hidden;
    pair<string, double> base;   //基准货币及其与美元的汇率
    pair<string, double> exchg;  //用于换算的货币
    map<string, double> exchg_rates;    //汇率映射表



    static void cb_curr1_button(Address, Address pw) { reference_to<Currency_exchg_rate_window>(pw).curr1_button_pressed(); }
    static void cb_curr2_button(Address, Address pw) { reference_to<Currency_exchg_rate_window>(pw).curr2_button_pressed(); }
    static void cb_curr1_menu(Address pb, Address pw)
    {
        //string cur_name = reference_to<Fl_Widget>(pb).label();
     reference_to<Currency_exchg_rate_window>(pw).curr1_menu_pressed(reference_to<Fl_Widget>(pb).label());
    }
    static void cb_curr2_menu(Address pb, Address pw)
    {
        reference_to<Currency_exchg_rate_window>(pw).
    		curr2_menu_pressed(reference_to<Fl_Widget>(pb).label());
    }


    void curr1_button_pressed();
    void curr2_button_pressed();
    void curr1_menu_pressed(const string&);
    void curr2_menu_pressed(const string&);


public:
    Currency_exchg_rate_window(Point xy, int w, int h,
        const string& fname = "exchange_rate.txt",
        const string& tt = "Currentcy Exchange Rate Window");

};
//------------------------------------------------------------------------------
static void skip_line(istream& is)
{
 //功能:跳过该行,即读入并忽略改行剩余字符
    char ch{ 0 };
    while (is.get(ch) && ch != '\n')
        continue;

}
void read_exchange_rate(const string& fname, map<string, double>& er)
{
    //功能:从汇率文件读入汇率到映射表中,如果遇到文件中的注释则跳过改行,
    //注释是以'#'开始的一行
    ifstream ifs{ fname };
    if (!ifs)
        error("Can't open ", fname);
    char ch{ 0 };
    while(ifs >> ch)
    {
        if (ch == '#')
            skip_line(ifs);
        else
        {
            ifs.putback(ch);
            string cur;
            double rate{ -1.0 };
            ifs >> cur >> rate;
            if (cur == "")
                error("Read currency name failed");
            else if (rate <= 0) //确保不读入0或者负数
                error("Invalid rate: non-positive");
            else
                er[cur] = rate;
        }
    }
}

Currency_exchg_rate_window::Currency_exchg_rate_window(Point xy, int w, int h,
                                                       const string& fname, const string& tt)
 :My_window(xy, w, h, tt),
curr1_button(Point{x_max()/2-150, y_max()/3},100,30,"Currency 1", cb_curr1_button),
curr2_button(Point{x_max()/2+50, y_max()/3},100,30,"Currency 2", cb_curr2_button),
    curr1_menu(Point{ curr1_button.loc.x,curr1_button.loc.y + curr1_button.height }, curr1_button.width, curr1_button.height, Menu::vertical, "Currency 1 menu"),
curr2_menu(Point{ curr2_button.loc.x,curr2_button.loc.y + curr2_button.height }, curr2_button.width,curr2_button.height,Menu::vertical,"Currency 2 menu"),
rate1(Point{curr1_button.loc.x, curr1_button.loc.y + curr1_button.height + 30},curr1_button.width,curr1_button.height,""),
rate2(Point{curr2_button.loc.x, curr2_button.loc.y + curr2_button.height + 30},curr2_button.width,curr2_button.height,""),
    is_menu1_hidden{true}, is_menu2_hidden{true},
base{"",0.0}, exchg{"", 0.0}
{
    read_exchange_rate(fname, exchg_rates);
    next_button.hide(); //不需要next按钮
    attach(curr1_button);
    attach(curr2_button);
    attach(rate1);
    attach(rate2);
    rate1.put("1");
	//接下来根据读入的汇率创建货币菜单
    for (const auto& p : exchg_rates)
    {
        curr1_menu.attach(new Button{ Point{0,0},0,0,p.first,cb_curr1_menu });
        curr2_menu.attach(new Button{ Point{0,0},0,0,p.first,cb_curr2_menu });
    }
    attach(curr1_menu);
    attach(curr2_menu);
    curr1_menu.hide();
    curr2_menu.hide();
}

void Currency_exchg_rate_window::curr1_button_pressed()
{
    if (is_menu1_hidden)
        curr1_menu.show();
    else
        curr1_menu.hide();
    is_menu1_hidden = !is_menu1_hidden;

    //还会影响菜单2
    if (!is_menu2_hidden)
    {
        curr2_menu.hide();
        is_menu2_hidden = true;
    }
}

void Currency_exchg_rate_window::curr2_button_pressed()
{
    if (is_menu2_hidden)
        curr2_menu.show();
    else
        curr2_menu.hide();
    is_menu2_hidden = !is_menu2_hidden;

    //还会影响菜单1
    if (!is_menu1_hidden)
    {
        curr1_menu.hide();
        is_menu1_hidden = true;
    }
}

void Currency_exchg_rate_window::curr1_menu_pressed(const string& curr_name)
{
    //设置基准货币,隐藏菜单,显示带有当前货币名的按钮,计算并输出新的汇率
    base.first = curr_name;
    base.second = exchg_rates[curr_name];

    curr1_menu.hide();
    is_menu1_hidden = true;

    curr1_button.label = curr_name;

    if(exchg.second > 0.0)
    {
        double res = exchg.second / base.second;
        ostringstream oss;
        oss << res;
        rate2.put(oss.str());
    }

}

void Currency_exchg_rate_window::curr2_menu_pressed(const string& curr_name)
{
    //设置基准货币,隐藏菜单,显示带有当前货币名的按钮,计算并输出新的汇率
    exchg.first = curr_name;
    exchg.second = exchg_rates[curr_name];

    curr2_menu.hide();
    is_menu2_hidden = true;

    curr2_button.label = curr_name;

    if (base.second > 0.0)
    {
        double res = exchg.second / base.second;
        ostringstream oss;
        oss << res;
        rate2.put(oss.str());
    }
}

21.9 Calculator GUI

首先是经过修改后的 calculator.cpp

/*
	calculator.cpp

	本程序实现了一个简单的表达式计算器,支持变量操作
	相比第七章的计算机,
	从In_box读入,输出到Out_box,
	取消了 quit 符号,
	虽然没有取消 print 符号,但是用户自己输入print符号是非法的	

	输入文法如下:
		Calculation:
			Statement
			Print
			Quit
			Calculation Statement

		Print:
			';'

		Quit:
			'q'
			"quit"

		Statement:
			Declaration
			Expression

		Declaration:
			"let" name '=' Expression
			"const" name '=' Expression

		Expression:
			Term
			Expression '+' Term
			Expression '-' Term

		Term:
			Primary
			Term * Primary
			Term / Primary
			Term % Primary

		Primary:
			Number
			'(' Expression ')'
			- Primary	//处理负数
			Assignment

		Number:
			floating-point-literal

		Assignment:
			name '=' Expression

*/


#include "My_window.h"

struct Token {
	char kind;
	double value;
	string name;
	Token(char ch) :kind(ch), value(0) { }
	Token(char ch, double val) :kind(ch), value(val) { }
	Token(char ch, string n) :kind(ch), name(n) { }
};

class Token_stream {
private:
	bool full;
	Token buffer;
public:
	Token_stream() :full(false), buffer(0) { }	// The constructor just sets full to indicate that the buffer is empty:

	Token get(istream& is);
	void putback(Token t);
	void ignore(istream&, char);
};

const string declkey = "let";
const char let = 'L';
//const string quitkey = "quit";
//const char quit = 'q';
const char print = ';';
const char number = '8';
const char name = 'a';
const char assign = '=';
const char con = 'C';
const string constkey = "const";

Token Token_stream::get(istream& is)
{
	if (full)
	{
		full = false;
		return buffer;
	}
	char ch;
	if (!(is >> ch))	// note that >> skips whitespace (space, newline, tab, etc.)
		return Token{ print };
	
	switch (ch) {
	case '(': case ')':
	case '+': case '-':
	case '*': case '/': case '%':
	case '=':
	//case quit:
	//case print:
		return Token{ ch };	// let each character represent itself
	case '.':
	case '0': case '1': case '2': case '3':	case '4':
	case '5': case '6': case '7': case '8': case '9':
	{
		is.putback(ch);	// put digit back into the input stream
		//is.unget();	// same as putback(char), except no parameter
		double val;
		is >> val;		// read a floating-point number
		return Token{ number, val };
	}
	default:
		if (isalpha(ch)) {
			string s;
			s += ch;
			//名字为字母开头的,带字母数字和下划线的字符串
			while (is.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_'))
				s += ch;
			is.putback(ch);
			if (s == declkey) return Token{ let };	// 声明变量关键字
			if (s == constkey) return Token{ con };	// 声明常量关键字
			//if (s == quitkey) return Token{ quit };
			return Token{ name, s };
		}
		error("Bad token");
	}
}

// The putback() member function puts its argument back into the Token_stream's buffer:
void Token_stream::putback(Token t)
{
	if (full)
		error("putback() into a full buffer.");
	buffer = t;
	full = true;
}

void Token_stream::ignore(istream& is, char c)
{
	if (full && c == buffer.kind) {
		full = false;
		return;
	}
	full = false;

	char ch;
	while (is >> ch)
		if (ch == c) return;
}


//---------------------------------------------------------
// set Variable
class Variable {
public:
	string name;
	double value;
	bool is_const;
	Variable(string n, double v, bool b) :name(n), value(v), is_const(b) { }
};

vector<Variable> var_table;

double get_value(string s)
{
	for (const Variable& v : var_table)
		if (v.name == s)
			return v.value;
	error("get: undefined name ", s);
}

void set_value(string s, double d)
{
	for (Variable& v : var_table)
		if (v.name == s)
		{
			if (v.is_const)
				error("set: can not assign to a const ", s);
			v.value = d;
			return;
		}
	error("set: undefined name ", s);
}

bool is_declared(string var)
{
	//判断var是否已经在var_table中了
	for (const Variable& v : var_table)
		if (v.name == var)
			return true;
	return false;
}

double define_name(string var, double val, bool is_const)
{
	//将(var, val)加入var_table中
	if (is_declared(var))
		error(var, " declared twice");
	var_table.push_back(Variable{ var, val, is_const });
	return val;
}

Token_stream ts;

//--------Expression---------------
double expression(istream& is);

double assignment(istream& is, string var)
{
	// 函数假设已经读入了赋值语句的左值和赋值符号

	double right = expression(is);
	set_value(var, right);
	return right;
}

double primary(istream& is)
{
	Token t = ts.get(is);
	switch (t.kind) {
	case '(':
	{
		double d = expression(is);
		t = ts.get(is);
		if (t.kind != ')')
			error("'(' expected");
		return d;
	}
	case '-':
		return -primary(is);
	case number:
		return t.value;
	case name:
	{
		Token t2 = ts.get(is);
		if (t2.kind != assign)
		{
			ts.putback(t2);
			return get_value(t.name);	// if next char is not a assignment operator, then return variable value
		}
		else
			return assignment(is, t.name);
	}
	default:
		error("primary expected");
	}
}

double term(istream& is)
{
	double left = primary(is);
	while (true) {
		Token t = ts.get(is);
		switch (t.kind) {
		case '*':
			left *= primary(is);
			break;
		case '/':
		{
			double d = primary(is);
			if (d == 0) error("divide by zero");
			left /= d;
			break;
		}
		case '%':
		{
			double d = primary(is);
			if (d == 0) error("divide by zero");
			left = fmod(left, d);
			break;
		}
		default:
			ts.putback(t);
			return left;
		}
	}
}

double expression(istream& is)
{
	double left = term(is);
	while (true) {
		Token t = ts.get(is);
		switch (t.kind) {
		case '+':
			left += term(is);
			break;
		case '-':
			left -= term(is);
			break;
		case print:
			return left;
		default:
			ts.putback(t);
			return left;
		}
	}
}

double declaration(istream& is, char kind)
{
	Token t = ts.get(is);
	if (t.kind != name)
		error("name expected in declaration");
	string var_name = t.name;

	Token t2 = ts.get(is);
	if (t2.kind != '=')
		error("= missing in declaration of ", var_name);

	double d = expression(is);
	bool is_const = kind == con;
	define_name(var_name, d, is_const);

	return d;
}

extern double statement(istream& is)
{
	Token t = ts.get(is);
	while (t.kind == print) t = ts.get(is);
	switch (t.kind) {
	case let:
	case con:
		return declaration(is, t.kind);
	default:
		ts.putback(t);
		return expression(is);
	}
}

//void clean_up_mess()
//{
//	ts.ignore(print);
//}

接下来是计算器极度简陋的图形化界面的实现

class Calculator_window : public My_window
{
public:
   Calculator_window(Point xy, int w, int h, const string& tt = "Calculator GUI");

private:
   In_box stat;    //输入的声明语句
   Out_box res;    //输出结果
   //Button cal;   //用next按钮代替

   void next() override;
};
//------------------------------------------------------------------------------
Calculator_window::Calculator_window(Point xy, int w, int h, const string& tt)
   :My_window(xy, w, h, tt),
	stat{Point{xy.x + 50, xy.y + 50}, 200, 20, "statement"},
  	res{Point{xy.x + 50, xy.y + 80}, 200, 20, "result"}
   //cal{ Point{xy.x + 170, xy.y + 50}, 50, 20, "calculate", }
{
   attach(stat);
   attach(res);
   //修改next按钮的位置和名称
   Point next_loc = next_button.loc;
   int dx = stat.loc.x + stat.width + 20 - next_loc.x;
   int dy = stat.loc.y - next_loc.y;
   next_button.move(dx, dy);
   next_button.label = "calculate";
}

void Calculator_window::next()
{
   istringstream iss{ stat.get_string() };
   ostringstream oss;
	try {
       double result = statement(iss);
       oss << result;
   }
   catch (runtime_error& e) {
       oss << e.what();
   }
   res.put(oss.str());
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值