1namespace Caribou {
2    /**
3     * Object providing access to keyboard in scanning mode.
4     */
5    public class Scanner : Object {
6        public bool bind_settings { get; construct; default=true; }
7
8        private ScanGrouping _scan_grouping;
9        public int scan_grouping {
10            get {
11                return (int) _scan_grouping;
12            }
13
14            set construct {
15                _scan_grouping = (ScanGrouping) value;
16                if (root_group != null)
17                    root_group.scan_grouping = _scan_grouping;
18                reset ();
19            }
20        }
21
22        private bool _scan_enabled;
23        public bool scan_enabled {
24            get { return _scan_enabled; }
25            set construct {
26                _scan_enabled = value;
27                if (_scan_enabled)
28                    enable ();
29                else
30                    disable ();
31            }
32        }
33
34        private double _step_time;
35        public double step_time {
36            get { return _step_time; }
37            set construct {
38                _step_time = value;
39                if (scan_tid != 0) {
40                    GLib.Source.remove (scan_tid);
41                    scan_tid = GLib.Timeout.add ((int) (_step_time*1000), scan);
42                }
43            }
44        }
45
46        private string _switch_device;
47        public string switch_device {
48            get { return _switch_device; }
49            set construct {
50                _switch_device = value;
51                configure_switch ();
52            }
53        }
54
55        private string _keyboard_key;
56        public string keyboard_key {
57            get { return _keyboard_key; }
58            set construct {
59                _keyboard_key = value;
60                configure_switch ();
61            }
62        }
63
64        private int _mouse_button;
65        public int mouse_button {
66            get { return _mouse_button; }
67            set construct {
68                _mouse_button = value;
69                configure_switch ();
70            }
71        }
72
73        public int scan_cycles { get; set construct; default=1; }
74        public bool autorestart { get; set construct; default=false; }
75        public bool inverse_scanning { get; set construct; default=false; }
76
77        private delegate void UnconfigureSwitchFunc();
78        private UnconfigureSwitchFunc unconfigure_switch_func;
79
80        private uint scan_tid;
81        private KeyboardModel keyboard;
82        private IScannableGroup root_group;
83        private bool started;
84
85        construct {
86            /* defaults */
87            _scan_grouping = ScanGrouping.SUBGROUPS;
88            _step_time = 1.0;
89            _switch_device = "keyboard";
90
91            if (bind_settings)
92                do_bind_settings ();
93        }
94
95        private void do_bind_settings () {
96            Settings caribou_settings = new Settings ("org.gnome.caribou");
97            string[] settings = {"scan-grouping", "step-time", "scan-cycles",
98                                 "autorestart", "inverse-scanning", "switch-device",
99                                 "keyboard-key", "mouse-button", "scan-enabled"};
100
101            foreach (string setting in settings) {
102                caribou_settings.bind (setting, this, setting, SettingsBindFlags.GET);
103            }
104        }
105
106        private bool select () {
107            IScannableItem? item = root_group.child_select ();
108
109            if (item is IScannableGroup) {
110                step ();
111                return true;
112            } else {
113                reset ();
114                return false;
115            }
116        }
117
118        private void switch_pressed (uint code, bool pressed) {
119            if (pressed) {
120                if (!started) {
121                    step ();
122                    start ();
123                    return;
124                }
125
126                stop ();
127
128                if (inverse_scanning) {
129                    step ();
130                    start ();
131                } else {
132                    if (select () || autorestart)
133                        start ();
134                }
135            }
136        }
137
138        public void set_keyboard (KeyboardModel keyboard) {
139            GroupModel group = keyboard.get_group(keyboard.active_group);
140            this.keyboard = keyboard;
141            this.keyboard.notify["active-group"].connect(on_group_changed);
142            set_active_level (group.get_level (group.active_level));
143
144            foreach (string group_name in keyboard.get_groups()) {
145                group = keyboard.get_group(group_name);
146                group.notify["active-level"].connect(on_level_changed);
147            }
148        }
149
150        private void on_level_changed (Object obj, ParamSpec prop) {
151            GroupModel group = (GroupModel) obj;
152            set_active_level (group.get_level (group.active_level));
153        }
154
155        private void on_group_changed (Object obj, ParamSpec prop) {
156            GroupModel group = keyboard.get_group(keyboard.active_group);
157            set_active_level (group.get_level (group.active_level));
158        }
159
160        private void set_active_level (LevelModel level) {
161            root_group = (IScannableGroup) level;
162            root_group.scan_grouping = _scan_grouping;
163        }
164
165        public void reset () {
166            if (root_group != null)
167                root_group.scan_reset ();
168        }
169
170        private void enable () {
171            configure_switch ();
172        }
173
174        private void disable () {
175            unconfigure_switch ();
176        }
177
178        private void start () {
179            if (started || root_group == null)
180                return;
181
182            started = true;
183
184            scan_tid = GLib.Timeout.add((int) (_step_time*1000), scan);
185        }
186
187        private void stop () {
188            if (!started)
189                return;
190
191            started = false;
192            if (scan_tid != 0)
193                GLib.Source.remove (scan_tid);
194            scan_tid = 0;
195        }
196
197        private IScannableItem? step () {
198            IScannableItem item = root_group.child_step (scan_cycles);
199
200            if (item == null) {
201                reset ();
202            }
203
204            return item;
205        }
206
207        private bool scan () {
208            if (inverse_scanning)
209                select ();
210            else
211                return (step () != null);
212
213            return true;
214        }
215
216        private void unconfigure_switch () {
217            if (unconfigure_switch_func != null)
218                unconfigure_switch_func ();
219            unconfigure_switch_func = null;
220        }
221
222        private void configure_switch () {
223            if (!scan_enabled)
224                return;
225
226            unconfigure_switch ();
227
228            DisplayAdapter xadapter = DisplayAdapter.get_default();
229            if (switch_device == "keyboard" && keyboard_key != null) {
230                uint keyval = Gdk.keyval_from_name (keyboard_key);
231                xadapter.register_key_func (keyval, switch_pressed);
232                unconfigure_switch_func = () => {
233                    xadapter.register_key_func (keyval, null);
234                };
235            } else if (switch_device == "mouse" && mouse_button != 0) {
236                xadapter.register_button_func (mouse_button, switch_pressed);
237                unconfigure_switch_func = () => {
238                    xadapter.register_key_func (mouse_button, null);
239                };
240            }
241        }
242    }
243}