@@ -12,7 +12,8 @@ use std::{
12
12
RefCell ,
13
13
} ,
14
14
collections:: HashMap ,
15
- fs,
15
+ ffi:: OsStr ,
16
+ fs:: { self , File } ,
16
17
path:: {
17
18
Path ,
18
19
} ,
@@ -23,21 +24,26 @@ use std::{
23
24
use crate :: daemon:: Daemon ;
24
25
use crate :: keyboard:: Keyboard as ColorKeyboard ;
25
26
use crate :: keyboard_color_button:: KeyboardColorButton ;
27
+ use crate :: keymap:: KeyMap ;
28
+ use super :: error_dialog:: error_dialog;
26
29
use super :: key:: Key ;
27
30
use super :: layout:: Layout ;
28
31
use super :: page:: Page ;
29
32
use super :: picker:: Picker ;
30
33
31
34
pub struct KeyboardInner {
35
+ board : OnceCell < String > ,
32
36
daemon : OnceCell < Rc < dyn Daemon > > ,
33
37
daemon_board : OnceCell < usize > ,
34
38
keymap : OnceCell < HashMap < String , u16 > > ,
35
39
keys : OnceCell < Box < [ Key ] > > ,
40
+ load_button : gtk:: Button ,
36
41
page : Cell < Page > ,
37
42
picker : RefCell < WeakRef < Picker > > ,
38
43
selected : Cell < Option < usize > > ,
39
44
color_button_bin : gtk:: Frame ,
40
45
brightness_scale : gtk:: Scale ,
46
+ save_button : gtk:: Button ,
41
47
toolbar : gtk:: Box ,
42
48
hbox : gtk:: Box ,
43
49
stack : gtk:: Stack ,
@@ -88,26 +94,41 @@ impl ObjectSubclass for KeyboardInner {
88
94
..set_stack( Some ( & stack) ) ;
89
95
} ;
90
96
91
- let toolbar = cascade ! {
97
+ let toolbar = cascade ! {
92
98
gtk:: Box :: new( gtk:: Orientation :: Horizontal , 8 ) ;
93
99
..set_center_widget( Some ( & stack_switcher) ) ;
94
100
} ;
95
101
102
+ let load_button = cascade ! {
103
+ gtk:: Button :: with_label( "Load" ) ;
104
+ ..set_valign( gtk:: Align :: Center ) ;
105
+ } ;
106
+
107
+ let save_button = cascade ! {
108
+ gtk:: Button :: with_label( "Save" ) ;
109
+ ..set_valign( gtk:: Align :: Center ) ;
110
+ } ;
111
+
96
112
let hbox = cascade ! {
97
113
gtk:: Box :: new( gtk:: Orientation :: Horizontal , 8 ) ;
98
114
..add( & brightness_label) ;
99
115
..add( & brightness_scale) ;
100
116
..add( & color_label) ;
101
117
..add( & color_button_bin) ;
118
+ ..add( & load_button) ;
119
+ ..add( & save_button) ;
102
120
} ;
103
121
104
122
Self {
123
+ board : OnceCell :: new ( ) ,
105
124
daemon : OnceCell :: new ( ) ,
106
125
daemon_board : OnceCell :: new ( ) ,
107
126
keymap : OnceCell :: new ( ) ,
108
127
keys : OnceCell :: new ( ) ,
128
+ load_button,
109
129
page : Cell :: new ( Page :: Layer1 ) ,
110
130
picker : RefCell :: new ( WeakRef :: new ( ) ) ,
131
+ save_button,
111
132
selected : Cell :: new ( None ) ,
112
133
color_button_bin,
113
134
brightness_scale,
@@ -149,7 +170,7 @@ glib_wrapper! {
149
170
}
150
171
151
172
impl Keyboard {
152
- pub fn new < P : AsRef < Path > > ( dir : P , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
173
+ pub fn new < P : AsRef < Path > > ( dir : P , board : & str , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
153
174
let dir = dir. as_ref ( ) ;
154
175
155
176
let keymap_csv = fs:: read_to_string ( dir. join ( "keymap.csv" ) )
@@ -158,10 +179,10 @@ impl Keyboard {
158
179
. expect ( "Failed to load layout.csv" ) ;
159
180
let physical_json = fs:: read_to_string ( dir. join ( "physical.json" ) )
160
181
. expect ( "Failed to load physical.json" ) ;
161
- Self :: new_data ( & keymap_csv, & layout_csv, & physical_json, daemon, daemon_board)
182
+ Self :: new_data ( board , & keymap_csv, & layout_csv, & physical_json, daemon, daemon_board)
162
183
}
163
184
164
- fn new_layout ( layout : Layout , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
185
+ fn new_layout ( board : & str , layout : Layout , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
165
186
let keyboard: Self = glib:: Object :: new ( Self :: static_type ( ) , & [ ] )
166
187
. unwrap ( )
167
188
. downcast ( )
@@ -191,6 +212,7 @@ impl Keyboard {
191
212
}
192
213
let _ = keyboard. inner ( ) . keys . set ( keys. into_boxed_slice ( ) ) ;
193
214
215
+ let _ = keyboard. inner ( ) . board . set ( board. to_string ( ) ) ;
194
216
let _ = keyboard. inner ( ) . daemon . set ( daemon) ;
195
217
let _ = keyboard. inner ( ) . daemon_board . set ( daemon_board) ;
196
218
let _ = keyboard. inner ( ) . keymap . set ( layout. keymap ) ;
@@ -216,19 +238,23 @@ impl Keyboard {
216
238
217
239
pub fn new_board ( board : & str , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Option < Self > {
218
240
Layout :: from_board ( board) . map ( |layout|
219
- Self :: new_layout ( layout, daemon, daemon_board)
241
+ Self :: new_layout ( board , layout, daemon, daemon_board)
220
242
)
221
243
}
222
244
223
- fn new_data ( keymap_csv : & str , layout_csv : & str , physical_json : & str , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
245
+ fn new_data ( board : & str , keymap_csv : & str , layout_csv : & str , physical_json : & str , daemon : Rc < dyn Daemon > , daemon_board : usize ) -> Self {
224
246
let layout = Layout :: from_data ( keymap_csv, layout_csv, physical_json) ;
225
- Self :: new_layout ( layout, daemon, daemon_board)
247
+ Self :: new_layout ( board , layout, daemon, daemon_board)
226
248
}
227
249
228
250
fn inner ( & self ) -> & KeyboardInner {
229
251
KeyboardInner :: from_instance ( self )
230
252
}
231
253
254
+ fn board ( & self ) -> & str {
255
+ self . inner ( ) . board . get ( ) . unwrap ( )
256
+ }
257
+
232
258
fn daemon ( & self ) -> & Rc < dyn Daemon > {
233
259
self . inner ( ) . daemon . get ( ) . unwrap ( )
234
260
}
@@ -241,6 +267,10 @@ impl Keyboard {
241
267
self . inner ( ) . keymap . get ( ) . unwrap ( )
242
268
}
243
269
270
+ fn window ( & self ) -> Option < gtk:: Window > {
271
+ self . get_toplevel ( ) ?. downcast ( ) . ok ( )
272
+ }
273
+
244
274
pub fn layer ( & self ) -> usize {
245
275
//TODO: make this more robust
246
276
match self . inner ( ) . page . get ( ) {
@@ -290,30 +320,130 @@ impl Keyboard {
290
320
self . set_selected ( self . selected ( ) ) ;
291
321
}
292
322
323
+ pub fn export_keymap ( & self ) -> KeyMap {
324
+ let mut map = HashMap :: new ( ) ;
325
+ for key in self . keys ( ) {
326
+ let scancodes = key. scancodes . borrow ( ) ;
327
+ let scancodes = scancodes. iter ( ) . map ( |s| s. 1 . clone ( ) ) . collect ( ) ;
328
+ map. insert ( key. logical_name . clone ( ) , scancodes) ;
329
+ }
330
+ KeyMap {
331
+ board : self . board ( ) . to_string ( ) ,
332
+ map : map,
333
+ }
334
+ }
335
+
336
+ pub fn import_keymap ( & self , keymap : & KeyMap ) {
337
+ // TODO: don't block UI thread
338
+ // TODO: Ideally don't want this function to be O(Keys^2)
339
+
340
+ if & keymap. board != self . board ( ) {
341
+ error_dialog ( & self . window ( ) . unwrap ( ) ,
342
+ "Failed to import keymap" ,
343
+ format ! ( "Keymap is for board '{}'" , keymap. board) ) ;
344
+ return ;
345
+ }
346
+
347
+ for ( k, v) in keymap. map . iter ( ) {
348
+ let n = self
349
+ . keys ( )
350
+ . iter ( )
351
+ . position ( |i| & i. logical_name == k)
352
+ . unwrap ( ) ;
353
+ for ( layer, scancode_name) in v. iter ( ) . enumerate ( ) {
354
+ self . keymap_set ( n, layer, scancode_name) ;
355
+ }
356
+ }
357
+ }
358
+
293
359
fn connect_signals ( & self ) {
294
360
let kb = self ;
295
361
296
- self . inner ( ) . stack . connect_property_visible_child_notify ( clone ! ( @weak kb => @default -panic, move |stack| {
297
- let page: Option <Page > = match stack. get_visible_child( ) {
298
- Some ( child) => unsafe { child. get_data( "keyboard_confurator_page" ) . cloned( ) } ,
299
- None => None ,
362
+ self . inner ( ) . stack . connect_property_visible_child_notify (
363
+ clone ! ( @weak kb => @default -panic, move |stack| {
364
+ let page: Option <Page > = match stack. get_visible_child( ) {
365
+ Some ( child) => unsafe { child. get_data( "keyboard_confurator_page" ) . cloned( ) } ,
366
+ None => None ,
367
+ } ;
368
+
369
+ println!( "{:?}" , page) ;
370
+ let last_layer = kb. layer( ) ;
371
+ kb. inner( ) . page. set( page. unwrap_or( Page :: Layer1 ) ) ;
372
+ if kb. layer( ) != last_layer {
373
+ kb. set_selected( kb. selected( ) ) ;
374
+ }
375
+ } ) ,
376
+ ) ;
377
+
378
+ self . inner ( ) . brightness_scale . connect_value_changed (
379
+ clone ! ( @weak kb => @default -panic, move |this| {
380
+ let value = this. get_value( ) as i32 ;
381
+ if let Err ( err) = kb. daemon( ) . set_brightness( kb. daemon_board( ) , value) {
382
+ eprintln!( "{}" , err) ;
383
+ }
384
+ println!( "{}" , value) ;
385
+ } ) ,
386
+ ) ;
387
+
388
+ self . inner ( ) . load_button . connect_clicked ( clone ! ( @weak kb => @default -panic, move |_button| {
389
+ let filter = cascade! {
390
+ gtk:: FileFilter :: new( ) ;
391
+ ..set_name( Some ( "JSON" ) ) ;
392
+ ..add_mime_type( "application/json" ) ;
393
+ ..add_pattern( "*.json" ) ;
394
+ } ;
395
+
396
+ let chooser = cascade! {
397
+ gtk:: FileChooserNative :: new:: <gtk:: Window >( Some ( "Load Layout" ) , None , gtk:: FileChooserAction :: Open , Some ( "Load" ) , Some ( "Cancel" ) ) ;
398
+ ..add_filter( & filter) ;
300
399
} ;
301
400
302
- println!( "{:?}" , page) ;
303
- let last_layer = kb. layer( ) ;
304
- kb. inner( ) . page. set( page. unwrap_or( Page :: Layer1 ) ) ;
305
- if kb. layer( ) != last_layer {
306
- kb. set_selected( kb. selected( ) ) ;
401
+ if chooser. run( ) == gtk:: ResponseType :: Accept {
402
+ let path = chooser. get_filename( ) . unwrap( ) ;
403
+ match File :: open( & path) {
404
+ Ok ( file) => match KeyMap :: from_reader( file) {
405
+ Ok ( keymap) => kb. import_keymap( & keymap) ,
406
+ Err ( err) => error_dialog( & kb. window( ) . unwrap( ) , "Failed to import keymap" , err) ,
407
+ }
408
+ Err ( err) => error_dialog( & kb. window( ) . unwrap( ) , "Failed to open file" , err) ,
409
+ }
307
410
}
308
411
} ) ) ;
309
412
310
- self . inner ( ) . brightness_scale . connect_value_changed ( clone ! ( @weak kb => @default -panic, move |this| {
311
- let value = this. get_value( ) as i32 ;
312
- if let Err ( err) = kb. daemon( ) . set_brightness( kb. daemon_board( ) , value) {
313
- eprintln!( "{}" , err) ;
314
- }
315
- println!( "{}" , value) ;
413
+ self . inner ( ) . save_button . connect_clicked ( clone ! ( @weak kb => @default -panic, move |_button| {
414
+ let filter = cascade! {
415
+ gtk:: FileFilter :: new( ) ;
416
+ ..set_name( Some ( "JSON" ) ) ;
417
+ ..add_mime_type( "application/json" ) ;
418
+ ..add_pattern( "*.json" ) ;
419
+ } ;
420
+
421
+ let chooser = cascade! {
422
+ gtk:: FileChooserNative :: new:: <gtk:: Window >( Some ( "Save Layout" ) , None , gtk:: FileChooserAction :: Save , Some ( "Save" ) , Some ( "Cancel" ) ) ;
423
+ ..add_filter( & filter) ;
424
+ } ;
425
+
426
+ if chooser. run( ) == gtk:: ResponseType :: Accept {
427
+ let mut path = chooser. get_filename( ) . unwrap( ) ;
428
+ match path. extension( ) {
429
+ None => { path. set_extension( OsStr :: new( "json" ) ) ; }
430
+ Some ( ext) if ext == OsStr :: new( "json" ) => { }
431
+ Some ( ext) => {
432
+ let mut ext = ext. to_owned( ) ;
433
+ ext. push( ".json" ) ;
434
+ path. set_extension( & ext) ;
435
+ }
436
+ }
437
+ let keymap = kb. export_keymap( ) ;
316
438
439
+ match File :: create( & path) {
440
+ Ok ( file) => match keymap. to_writer_pretty( file) {
441
+ Ok ( ( ) ) => { } ,
442
+ Err ( err) => error_dialog( & kb. window( ) . unwrap( ) , "Failed to export keymap" , err) ,
443
+ }
444
+ Err ( err) => error_dialog( & kb. window( ) . unwrap( ) , "Failed to open file" , err) ,
445
+ }
446
+ }
317
447
} ) ) ;
318
448
}
319
449
0 commit comments