1
1
use gtk:: prelude:: * ;
2
- use gtk:: { self , Widget , Menu , TreeView , ListStore , Builder , MenuItem , TreeSelection , TargetEntry , TargetFlags , Stack } ;
2
+ use gtk:: { self , Widget , Menu , TreeView , ListStore , SelectionMode , Builder , MenuItem , TreeSelection , TreeIter , TargetEntry , TargetFlags , Stack } ;
3
+ use gtk:: Box as GBox ;
3
4
use gdk:: WindowExt ;
4
5
use gdk;
6
+ use gtk:: DragContextExtManual ;
5
7
use uuid:: Uuid ;
6
8
use std:: collections:: HashMap ;
7
9
use std:: mem;
10
+ use std:: cell:: Cell ;
11
+ use std:: rc:: Rc ;
8
12
use sync:: UISender ;
9
13
use sqa_backend:: codec:: { Command , Reply } ;
10
14
use sqa_backend:: mixer:: MixerConf ;
@@ -40,6 +44,7 @@ pub enum ActionMessageInner {
40
44
EditAction ,
41
45
ChangeName ( Option < String > ) ,
42
46
ChangePrewait ( Duration ) ,
47
+ Retarget ( Uuid ) ,
43
48
CloseButton ,
44
49
}
45
50
impl DurationEntryMessage for ActionMessageInner {
@@ -73,6 +78,7 @@ pub struct ActionController {
73
78
mexec : MenuItem ,
74
79
mcreate_audio : MenuItem ,
75
80
mcreate_fade : MenuItem ,
81
+ drag_notif : GBox ,
76
82
cur_page : Option < u32 > ,
77
83
sel_handler : u64
78
84
}
@@ -86,6 +92,7 @@ pub trait ActionUI {
86
92
fn on_action_list ( & mut self , _l : & HashMap < Uuid , OpaqueAction > ) { }
87
93
fn on_selection_finished ( & mut self , _sel : Uuid ) { }
88
94
fn on_selection_cancelled ( & mut self ) { }
95
+ fn is_dnd_target ( & self ) -> bool { false }
89
96
fn change_cur_page ( & mut self , Option < u32 > ) ;
90
97
}
91
98
@@ -95,13 +102,23 @@ struct TreeSelectGetter {
95
102
ts : ListStore
96
103
}
97
104
impl TreeSelectGetter {
105
+ pub fn iter_to_value ( & self , ti : TreeIter ) -> Option < Uuid > {
106
+ if let Some ( v) = self . ts . get_value ( & ti, 0 ) . get :: < String > ( ) {
107
+ if let Ok ( uu) = Uuid :: parse_str ( & v) {
108
+ return Some ( uu) ;
109
+ }
110
+ }
111
+ None
112
+ }
113
+ pub fn iter_is_dnd_target ( & self , ti : TreeIter ) -> bool {
114
+ if let Some ( v) = self . ts . get_value ( & ti, 7 ) . get :: < bool > ( ) {
115
+ return v;
116
+ }
117
+ false
118
+ }
98
119
pub fn get ( & self ) -> Option < Uuid > {
99
120
if let Some ( ( _, ti) ) = self . sel . get_selected ( ) {
100
- if let Some ( v) = self . ts . get_value ( & ti, 0 ) . get :: < String > ( ) {
101
- if let Ok ( uu) = Uuid :: parse_str ( & v) {
102
- return Some ( uu) ;
103
- }
104
- }
121
+ return self . iter_to_value ( ti)
105
122
}
106
123
None
107
124
}
@@ -129,11 +146,12 @@ impl ActionController {
129
146
let sel_handler = 0 ;
130
147
build ! ( ActionController using b
131
148
with ctls, opas, tx, mixer, cur_widget, cur_sel, cur_page, sel_handler
132
- get view, store, menu, medit, mload, mexec, mdelete, mcreate_audio, mcreate_fade, sidebar)
149
+ get view, store, menu, medit, mload, mexec, mdelete, mcreate_audio, mcreate_fade, sidebar, drag_notif )
133
150
}
134
151
pub fn bind ( & mut self , tx : & UISender ) {
135
152
use self :: ActionMessageInner :: * ;
136
153
self . tx = Some ( tx. clone ( ) ) ;
154
+ self . view . get_selection ( ) . set_mode ( SelectionMode :: Single ) ;
137
155
let tsg = TreeSelectGetter { ts : self . store . clone ( ) , sel : self . view . get_selection ( ) } ;
138
156
bind_action_menu_items ! {
139
157
self , tx, tsg,
@@ -160,13 +178,105 @@ impl ActionController {
160
178
self . mcreate_fade . connect_activate ( clone ! ( tx; |_| {
161
179
tx. send_internal( ActionInternalMessage :: Create ( "fade" ) ) ;
162
180
} ) ) ;
163
- let dnd_targets = vec ! [ TargetEntry :: new( "text/uri-list" , TargetFlags :: empty( ) , 0 ) ] ;
181
+ let row_dnd_target = TargetEntry :: new ( "STRING" , gtk:: TARGET_SAME_WIDGET , 0 ) ;
182
+ let dnd_targets = vec ! [
183
+ TargetEntry :: new( "text/uri-list" , TargetFlags :: empty( ) , 0 ) ,
184
+ row_dnd_target. clone( )
185
+ ] ;
186
+ self . view . drag_source_set ( gdk:: BUTTON1_MASK , & vec ! [ row_dnd_target] , gdk:: ACTION_COPY | gdk:: ACTION_MOVE ) ;
164
187
self . view . drag_dest_set ( gtk:: DEST_DEFAULT_ALL , & dnd_targets, gdk:: ACTION_COPY | gdk:: ACTION_MOVE ) ;
188
+ let dn = self . drag_notif . clone ( ) ;
189
+ let press_coords = Rc :: new ( Cell :: new ( ( 0.0 , 0.0 ) ) ) ;
190
+ let drag_uu: Rc < Cell < Option < Uuid > > > = Rc :: new ( Cell :: new ( None ) ) ;
191
+ self . view . connect_button_press_event ( clone ! ( press_coords; |_, eb| {
192
+ let coords = eb. get_position( ) ;
193
+ debug!( "dnd: set position to {:?}" , coords) ;
194
+ press_coords. set( coords) ;
195
+ Inhibit ( false )
196
+ } ) ) ;
197
+ self . view . connect_drag_begin ( clone ! ( dn, tsg, press_coords, drag_uu; |slf, dctx| {
198
+ let ( x, y) = press_coords. get( ) ;
199
+ let ( x, y) = ( x. trunc( ) as _, y. trunc( ) as _) ;
200
+ if let Some ( ( Some ( path) , _, _, _) ) = slf. get_path_at_pos( x, y) {
201
+ debug!( "dnd: drag begun" ) ;
202
+ dn. show_all( ) ;
165
203
166
- self . view . connect_drag_data_received ( clone ! ( tx; |_, _, _, _, data, _, _| {
204
+ if let Some ( ti) = tsg. ts. get_iter( & path) {
205
+ if let Some ( uu) = tsg. iter_to_value( ti) {
206
+ debug!( "dnd: dragging uu {}" , uu) ;
207
+ drag_uu. set( Some ( uu) ) ;
208
+ }
209
+ }
210
+ if let Some ( surf) = slf. create_row_drag_icon( & path) {
211
+ dctx. drag_set_icon_surface( & surf) ;
212
+ }
213
+ }
214
+ else {
215
+ debug!( "dnd: cancelling failed drag" ) ;
216
+ }
217
+ } ) ) ;
218
+ self . view . connect_drag_motion ( clone ! ( drag_uu, tsg; |slf, dctx, x, y, _| {
219
+ use gdk:: DragContextExtManual ;
220
+ if drag_uu. get( ) . is_some( ) {
221
+ let ( x, y) = slf. convert_widget_to_bin_window_coords( x, y) ;
222
+ if let Some ( ( Some ( path) , _, _, _) ) = slf. get_path_at_pos( x, y) {
223
+ if let Some ( ti) = tsg. ts. get_iter( & path) {
224
+ if !tsg. iter_is_dnd_target( ti) {
225
+ dctx. drag_status( gdk:: DragAction :: empty( ) , 0 ) ;
226
+ }
227
+ else {
228
+ dctx. drag_status( gdk:: ACTION_MOVE , 0 ) ;
229
+ }
230
+ return Inhibit ( false ) ;
231
+ }
232
+ }
233
+ dctx. drag_status( gdk:: DragAction :: empty( ) , 0 ) ;
234
+ }
235
+ else {
236
+ dctx. drag_status( gdk:: ACTION_COPY , 0 ) ;
237
+ }
238
+ Inhibit ( false )
239
+ } ) ) ;
240
+ self . view . connect_drag_end ( clone ! ( drag_uu; |_, _| {
241
+ debug!( "dnd: drag ended" ) ;
242
+ drag_uu. set( None ) ;
243
+ dn. hide( ) ;
244
+ } ) ) ;
245
+ self . view . connect_drag_data_get ( clone ! ( drag_uu; |_, _, data, _, _| {
246
+ if let Some ( uu) = drag_uu. get( ) {
247
+ debug!( "dnd: drag_data_get called for uu {}" , uu) ;
248
+ data. set_text( & format!( "{}" , uu) , -1 ) ;
249
+ }
250
+ else {
251
+ debug!( "dnd: drag_data_get called, but no uu" ) ;
252
+ }
253
+ } ) ) ;
254
+ self . view . connect_drag_data_received ( clone ! ( tx, tsg; |slf, _, x, y, data, _, _| {
167
255
let uris = data. get_uris( ) ;
168
256
debug!( "dnd: got uris {:?}" , uris) ;
169
- if uris. len( ) == 0 { return ; }
257
+ if uris. len( ) == 0 {
258
+ if let Some ( txt) = data. get_text( ) {
259
+ if let Ok ( uu) = txt. parse:: <Uuid >( ) {
260
+ debug!( "dnd: got UUID dropped {}, widget coords ({}, {})" , uu, x, y) ;
261
+ let ( x, y) = slf. convert_widget_to_bin_window_coords( x, y) ;
262
+ debug!( "dnd: bin window coords ({}, {})" , x, y) ;
263
+ if let Some ( ( Some ( path) , _, _, _) ) = slf. get_path_at_pos( x, y) {
264
+ if let Some ( ti) = tsg. ts. get_iter( & path) {
265
+ if let Some ( uu2) = tsg. iter_to_value( ti) {
266
+ debug!( "dnd: was dropped onto {}" , uu2) ;
267
+ tx. send_internal( ( uu2, ActionMessageInner :: Retarget ( uu) ) ) ;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ else {
273
+ return ;
274
+ }
275
+ }
276
+ else {
277
+ return ;
278
+ }
279
+ }
170
280
tx. send_internal( ActionInternalMessage :: FilesDropped ( uris) ) ;
171
281
} ) ) ;
172
282
@@ -194,26 +304,42 @@ impl ActionController {
194
304
Active ( _) => "gtk-media-play" ,
195
305
Errored ( _) => "gtk-dialog-error"
196
306
} ;
307
+ let mut duration_progress = 0 ;
197
308
let duration = match action. state {
198
309
Active ( Some ( ref dur) ) => {
199
310
let ( elapsed, pos) = dur. elapsed ( true ) ;
200
311
let text = if pos { "T+" } else { "T-" } ;
312
+ if pos && dur. est_duration . is_some ( ) {
313
+ let ed = dur. est_duration . unwrap ( ) ;
314
+ let ed = ( ed. as_secs ( ) as f32 * 1_000_000_000.0 ) + ed. subsec_nanos ( ) as f32 ;
315
+ let elapsed = ( elapsed. as_secs ( ) as f32 * 1_000_000_000.0 ) + elapsed. subsec_nanos ( ) as f32 ;
316
+ let mut perc = ( ( elapsed / ed) * 100.0 ) . trunc ( ) ;
317
+ if perc > 100.0 {
318
+ perc = 100.0 ;
319
+ }
320
+ duration_progress = perc as u32 ;
321
+ }
201
322
format ! ( "{}{}" , text, DurationEntry :: format( elapsed, false ) )
202
323
} ,
203
324
_ => "" . into ( )
204
325
} ;
326
+ let is_dnd_target = self . ctls . get ( & uu) . unwrap ( ) . is_dnd_target ( ) ;
205
327
let iter = self . store . insert_with_values ( None , & [
206
328
0 , // uuid
207
329
1 , // description
208
330
2 , // icon-state (playback state icon)
209
331
3 , // icon-type (action type icon)
210
332
4 , // duration
333
+ 6 , // duration progress bar (0-100 percent)
334
+ 7 , // is-dnd-target (can we drop other actions onto this one?)
211
335
] , & [
212
336
& uu. to_string ( ) ,
213
337
& action. display_name ( ) ,
214
338
& state,
215
339
& typ,
216
- & duration
340
+ & duration,
341
+ & duration_progress,
342
+ & is_dnd_target
217
343
] ) ;
218
344
if let Some ( u2) = sel {
219
345
if * uu == u2 {
@@ -407,6 +533,7 @@ impl ActionController {
407
533
tx. send ( Command :: UpdateActionMetadata { uuid : msg. 0 , meta : opa. meta . clone ( ) } ) ;
408
534
}
409
535
} ,
536
+ Retarget ( uu) => ctl. on_selection_finished ( uu) ,
410
537
CloseButton => ctl. close_window ( ) ,
411
538
x => ctl. on_message ( x)
412
539
}
0 commit comments