5 public List
<string> paste_queue
;
6 const string log_domain
= "Gmpc.DataView";
8 /** The Default column width. */
9 const int default_column_width
= 200;
13 * * List of column ids to show.
14 * * Name of each column
15 * * Default set of enabled columns.
17 const int NUM_COLS
= 20;
18 const int[] gmpc_data_view_col_ids
= {
19 Gmpc
.MpdData
.ColumnTypes
.MARKUP
,
20 Gmpc
.MpdData
.ColumnTypes
.SONG_ARTIST
, /* album name */
21 Gmpc
.MpdData
.ColumnTypes
.SONG_ALBUM
, /* album name */
22 Gmpc
.MpdData
.ColumnTypes
.SONG_TITLE
, /* song title */
23 Gmpc
.MpdData
.ColumnTypes
.SONG_TITLEFILE
, /* song title */
24 Gmpc
.MpdData
.ColumnTypes
.SONG_GENRE
, /* song genre */
25 Gmpc
.MpdData
.ColumnTypes
.SONG_TRACK
, /* song track */
26 Gmpc
.MpdData
.ColumnTypes
.SONG_NAME
, /* stream name */
27 Gmpc
.MpdData
.ColumnTypes
.SONG_COMPOSER
, /* composer name */
28 Gmpc
.MpdData
.ColumnTypes
.SONG_PERFORMER
, /* performer */
29 Gmpc
.MpdData
.ColumnTypes
.SONG_DATE
, /* date */
30 Gmpc
.MpdData
.ColumnTypes
.SONG_LENGTH_FORMAT
, /* length formatted */
31 Gmpc
.MpdData
.ColumnTypes
.SONG_DISC
, /* disc */
32 Gmpc
.MpdData
.ColumnTypes
.SONG_COMMENT
, /* comment */
33 Gmpc
.MpdData
.ColumnTypes
.ICON_ID
, /* icon id */
34 Gmpc
.MpdData
.ColumnTypes
.SONG_POS
,
35 Gmpc
.MpdData
.ColumnTypes
.SONG_ALBUMARTIST
,
36 Gmpc
.MpdData
.ColumnTypes
.PATH_EXTENSION
, /* Extension */
37 Gmpc
.MpdData
.ColumnTypes
.PATH_DIRECTORY
, /* Directory */
38 Gmpc
.MpdData
.ColumnTypes
.SONG_PRIORITY
40 const string[] gmpc_data_view_col_names
= {
63 const bool[] gmpc_data_view_col_enabled
= {
87 const int[] gmpc_data_view_col_position
= {
110 public class Gmpc
.DataView
: Gtk
.TreeView
112 private Gtk
.TreeViewColumn
[] tree_columns
= new Gtk
.TreeViewColumn
[NUM_COLS
];
114 * If we are a play-queue we should treat the content.
115 * slightly different.
116 * e.g. add-replace will be play-crop
118 public bool is_play_queue
{get; set; default=false;}
121 * The name of the treeview.
122 * This is used to store the column layout.
124 public string uid
{get; set; default="default";}
128 * Construction function.
130 public DataView(string name
, bool play_queue
= false)
132 log(log_domain
, LogLevelFlags
.LEVEL_INFO
, "Constructing dataview: "+name
);
133 this
.is_play_queue
= play_queue
;
135 // Connect row activated signal.
136 this
.row_activated
.connect(__row_activated
);
137 this
.key_press_event
.connect(__key_press_event_callback
);
138 this
.button_press_event
.connect(__button_press_event_callback
);
139 this
.button_release_event
.connect(__button_release_event_callback
);
140 // When it getst he destroy signal.
141 this
.destroy
.connect(column_store_state
);
143 this
.set_rules_hint(true);
144 this
.set_enable_search(false);
146 this
.get_selection().set_mode(Gtk
.SelectionMode
.MULTIPLE
);
147 this
.set_fixed_height_mode(true);
161 public void right_mouse_menu(Gtk
.Menu menu
)
163 int selected_rows
= this
.get_selection().count_selected_rows();
164 if(selected_rows
== 1) {
165 var item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.MEDIA_PLAY
,null);
166 item
.activate
.connect((source
)=>{
167 selected_songs_play();
171 item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.INFO
, null);
172 item
.activate
.connect((source
)=>{
173 selected_songs_info();
180 // Add play if there is one selected row.
181 if(selected_rows
> 0) {
182 var item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.REMOVE
,null);
183 item
.activate
.connect((source
)=>{
184 selected_songs_remove();
189 var item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.ADD
,null);
190 item
.activate
.connect((source
)=>{
191 selected_songs_add();
195 if(server
.check_command_allowed("prioid") == MPD
.Server
.Command
.ALLOWED
)
197 menu
.append( new Gtk
.SeparatorMenuItem());
198 var item
= new Gtk
.MenuItem
.with_label(_("Queue"));
199 item
.activate
.connect((source
)=>{ selected_songs_raise_priority();});
203 item
= new Gtk
.MenuItem
.with_label(_("Dequeue"));
204 item
.activate
.connect((source
)=>{ selected_songs_remove_priority();});
209 if(selected_rows
> 0)
212 item
= new Gtk
.SeparatorMenuItem();
215 item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.CUT
, null);
216 item
.activate
.connect((source
)=>{ selected_songs_paste_queue_cut();});
220 item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.Stock
.COPY
, null);
221 item
.activate
.connect((source
)=>{ selected_songs_paste_queue_copy();});
224 if(is_play_queue
&& paste_queue
!= null) {
225 item
= new Gtk
.ImageMenuItem
.with_label(_("Paste before"));
226 (item as Gtk
.ImageMenuItem
).set_image(new Image
.from_stock(Gtk
.Stock
.PASTE
, Gtk
.IconSize
.MENU
));
227 item
.activate
.connect((source
)=>{ selected_songs_paste_before();});
230 item
= new Gtk
.ImageMenuItem
.with_label(_("Paste after"));
231 (item as Gtk
.ImageMenuItem
).set_image(new Image
.from_stock(Gtk
.Stock
.PASTE
, Gtk
.IconSize
.MENU
));
232 item
.activate
.connect((source
)=>{ selected_songs_paste_after();});
237 if(selected_rows
== 1)
240 var list
= get_selection().get_selected_rows(out model
);
241 var path
= list
.first().data
;
243 if(model
.get_iter(out iter
, path
))
245 unowned MPD
.Song? song
;
246 model
.get(iter
, MpdData
.ColumnTypes
.MPDSONG
, out song
);
248 MpdInteraction
.submenu_for_song(menu
, song
);
253 // Tool menu integration.
254 bool initial
= false;
255 var item
= new Gtk
.MenuItem
.with_label(_("Tools"));
256 var smenu
= new Gtk
.Menu();
257 item
.set_submenu(smenu
);
258 for(int i
= 0; i
< Gmpc
.num_plugins
; i
++){
259 if(plugins
[i
].get_enabled() &&
260 plugins
[i
].browser_song_list_option_menu(this
,smenu
) > 0){
261 stdout
.printf("adding entry\n");
266 __button_press_menu
.append(new Gtk
.SeparatorMenuItem());
267 __button_press_menu
.append(item
);
272 * Internal functions.
276 * Store the position, visibility and width of the columns
278 private void column_store_state()
280 // Save the position of the columns
281 var columns
= get_columns();
283 foreach(var column
in columns
)
285 int col_index
= column
.get_data("index");
286 int width
= column
.get_width();
287 config
.set_int(uid
+"-colpos", gmpc_data_view_col_names
[col_index
], index
);
288 config
.set_bool(uid
+"-colshow", gmpc_data_view_col_names
[col_index
], column
.visible
);
289 // Only store width if bigger then 0.
291 config
.set_int(uid
+"-colsize",gmpc_data_view_col_names
[col_index
], width
);
296 // Hack to make vala not destroy the menu directly.
297 private Gtk
.Menu column_selection_menu
= null;
298 private void column_show_selection_menu()
300 column_selection_menu
= new Gtk
.Menu();
301 foreach(var col
in tree_columns
)
303 int index
= col
.get_data("index");
304 // Do not show the icon id in the selection list.
305 if(gmpc_data_view_col_ids
[index
] == MpdData
.ColumnTypes
.ICON_ID
) continue;
306 var item
= new Gtk
.CheckMenuItem
.with_label(FixGtk
.gettext(gmpc_data_view_col_names
[index
]));
308 item
.set_active(true);
310 // On activation toggle the state.
311 item
.activate
.connect((source
) => {
312 col
.visible
= (source as Gtk
.CheckMenuItem
).get_active();
314 column_selection_menu
.append(item
);
316 column_selection_menu
.show_all();
317 column_selection_menu
.popup(null, null, null, 0, Gtk
.get_current_event_time());
320 * Populate the treeview with the right columns.
321 * The treeview should have a name now.
323 private void column_populate()
325 for(int i
= 0; i
< NUM_COLS
; i
++)
327 Gtk
.TreeViewColumn col
= new Gtk
.TreeViewColumn();
328 col
.set_data("index", i
);
329 if(gmpc_data_view_col_ids
[i
] == Gmpc
.MpdData
.ColumnTypes
.ICON_ID
)
334 var renderer
= new Gtk
.CellRendererPixbuf();
335 renderer
.xalign
= 0.0f
;
337 col
.pack_start(renderer
, true);
338 col
.set_attributes(renderer
, "icon-name", Gmpc
.MpdData
.ColumnTypes
.ICON_ID
);
339 col
.set_resizable(false);
340 col
.set_fixed_width(20);
341 col
.clickable
= true;
342 // If the user clicks on the column, show dropdown allowing to enable/disable columns.
343 col
.clicked
.connect((source
) => {
344 column_show_selection_menu();
350 col
.set_title(FixGtk
.gettext(gmpc_data_view_col_names
[i
]));
351 var renderer
= new Gtk
.CellRendererText();
352 renderer
.ellipsize
= Pango
.EllipsizeMode
.END
;
355 renderer
.weight_set
= true;
356 renderer
.style_set
= true;
357 col
.set_cell_data_func(renderer
, highlight_row_current_song_playing
);
359 col
.pack_start(renderer
, true);
360 col
.set_attributes(renderer
, "text", gmpc_data_view_col_ids
[i
]);
361 col
.set_resizable(true);
363 int width
= config
.get_int_with_default(uid
+"-colsize",gmpc_data_view_col_names
[i
], default_column_width
);
364 // Do not set to size 0, then revert back to 200.
365 col
.set_fixed_width(width
>0?width
:default_column_width
);
367 col
.set_sizing(Gtk
.TreeViewColumnSizing
.FIXED
);
368 col
.set_reorderable(true);
371 int pos
= config
.get_int_with_default(uid
+"-colpos", gmpc_data_view_col_names
[i
], gmpc_data_view_col_position
[i
]);
372 this
.tree_columns
[pos
] = col
;
374 // Add the columns (in right order)
375 for(int i
= 0; i
< NUM_COLS
; i
++) {
376 int index
= this
.tree_columns
[i
].get_data("index");
377 this
.insert_column(this
.tree_columns
[i
], i
);
378 this
.tree_columns
[i
].set_visible(config
.get_bool_with_default(uid
+"-colshow", gmpc_data_view_col_names
[index
], gmpc_data_view_col_enabled
[index
]));
387 * Function handles the row-activate signal.
389 private void __row_activated (Gtk
.TreePath path
, Gtk
.TreeViewColumn col
)
391 Gtk
.TreeModel? model
= this
.get_model();
395 if(!model
.get_iter(out iter
, path
)) return;
397 /* If we are play-queue, play the selected song. */
399 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
400 MPD
.Player
.play_id(Gmpc
.server
, song_id
);
402 /* If we are a song browser, add the path and play it. */
404 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
405 MpdInteraction
.play_path(song_path
);
410 * Check if current row is playing.
412 private void highlight_row_current_song_playing(
414 Gtk
.CellRenderer renderer
,
418 // The current song we make bold.
419 if(model is Gmpc
.MpdData
.ModelPlaylist
&&
420 (model as Gmpc
.MpdData
.ModelPlaylist
).is_current_song(iter
)){
421 (renderer as Gtk
.CellRendererText
).weight
= Pango
.Weight
.BOLD
;
423 (renderer as Gtk
.CellRendererText
).weight
= Pango
.Weight
.NORMAL
;
426 // A prioritized song we make italic.
428 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_PRIORITY
, out prio
);
430 (renderer as Gtk
.CellRendererText
).style
= Pango
.Style
.ITALIC
;
432 (renderer as Gtk
.CellRendererText
).style
= Pango
.Style
.NORMAL
;
438 * Handle keyboard input.
440 private bool __key_press_event_callback_song_list(Gdk
.EventKey event
)
442 if(event
.keyval
== Gdk
.Key_i
)
444 return selected_songs_add();
446 else if (event
.keyval
== Gdk
.Key_r
)
452 private bool __key_press_event_callback_play_queue(Gdk
.EventKey event
)
454 if (event
.keyval
== Gdk
.Key_Q
)
457 return selected_songs_remove_priority();
459 else if (event
.keyval
== Gdk
.Key_c
)
461 // Cut (if available) into clipboard
462 selected_songs_paste_queue_cut();
464 else if (event
.keyval
== Gdk
.Key_P
)
467 return selected_songs_paste_before();
469 else if (event
.keyval
== Gdk
.Key_p
)
472 return selected_songs_paste_after();
474 else if (event
.keyval
== Gdk
.Key_d
)
476 if(!selected_songs_remove())
478 // Detach model (for some reason keeping it attached
479 // Makes thing break, work-around for now)
481 var model
= get_model();
484 MPD
.PlayQueue
.clear(server
);
492 private bool __key_press_event_callback(Gdk
.EventKey event
)
494 if(event
.keyval
== Gdk
.Key_j
)
499 else if (event
.keyval
== Gdk
.Key_k
)
504 else if(event
.keyval
== Gdk
.Key_y
)
506 // Copy data to clipboard
507 return selected_songs_paste_queue_copy();
509 else if (event
.keyval
== Gdk
.Key_o
)
511 return selected_songs_info();
513 else if (event
.keyval
== Gdk
.Key_Escape
)
517 else if (event
.keyval
== Gdk
.Key_m
)
520 column_show_selection_menu();
523 else if (event
.keyval
== Gdk
.Key_q
)
526 return selected_songs_raise_priority();
528 else if (event
.keyval
== Gdk
.Key_Menu
)
530 __button_press_menu
= new Gtk
.Menu();
532 right_mouse_menu(__button_press_menu
);
537 __button_press_menu
.show_all();
538 __button_press_menu
.popup(null, null,null,0, Gtk
.get_current_event_time());
542 // Commands specific to play_queue
545 if(__key_press_event_callback_play_queue(event
)) return true;
549 if(__key_press_event_callback_song_list(event
)) return true;
557 // Hack to stop vala from destroying my menu.
558 private Gtk
.Menu __button_press_menu
= null;
559 private bool __button_press_event_callback(Gdk
.EventButton event
)
561 if(event
.button
== 3)
563 __button_press_menu
= new Gtk
.Menu();
564 right_mouse_menu(__button_press_menu
);
566 __button_press_menu
.show_all();
567 __button_press_menu
.popup(null, null,null, event
.button
, event
.time
);
572 private bool __button_release_event_callback(Gdk
.EventButton event
)
579 * Interaction on selected songs.
581 // Set priority on the selected songs.
582 private bool selected_songs_raise_priority()
584 if(server
.check_command_allowed("prioid") != MPD
.Server
.Command
.ALLOWED
) return false;
585 var selection
= this
.get_selection();
587 if(selection
.count_selected_rows() > 254) {
588 Gmpc
.Messages
.show(_("You can only queue 254 songs at the time."), Gmpc
.Messages
.Level
.WARNING
);
594 foreach(var path
in selection
.get_selected_rows(out model
))
597 if(model
.get_iter(out iter
, path
))
600 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
602 MPD
.PlayQueue
.set_priority(server
, song_id
, priority
--);
605 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
606 song_id
= MPD
.PlayQueue
.add_song_get_id(server
,song_path
);
607 MPD
.PlayQueue
.set_priority(server
, song_id
, priority
--);
613 // Remove the set priority from the selected songs.
614 private bool selected_songs_remove_priority()
616 if(server
.check_command_allowed("prioid") != MPD
.Server
.Command
.ALLOWED
) return false;
617 var selection
= this
.get_selection();
620 foreach(var path
in selection
.get_selected_rows(out model
))
623 if(model
.get_iter(out iter
, path
))
626 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
627 MPD
.PlayQueue
.set_priority(server
, song_id
, 0);
632 // Add the selected song
633 private bool selected_songs_add()
636 var selection
= this
.get_selection();
639 foreach(var path
in selection
.get_selected_rows(out model
))
642 if(model
.get_iter(out iter
, path
))
645 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
646 MPD
.PlayQueue
.queue_add_song(server
, song_path
);
650 MPD
.PlayQueue
.queue_commit(server
);
656 // Play the selected song
657 private bool selected_songs_play()
659 var selection
= this
.get_selection();
661 foreach(var path
in selection
.get_selected_rows(out model
))
664 if(model
.get_iter(out iter
, path
))
668 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
670 MPD
.Player
.play_id(server
, song_id
);
675 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
676 MpdInteraction
.play_path(song_path
);
681 return selection
.count_selected_rows() > 0;
683 // Remove the selected songs from the play queue.
684 private bool selected_songs_remove()
686 int deleted_rows
= 0;
687 var selection
= this
.get_selection();
690 foreach(var path
in selection
.get_selected_rows(out model
))
693 if(model
.get_iter(out iter
, path
))
696 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
697 MPD
.PlayQueue
.queue_delete_id(server
, song_id
);
701 MPD
.PlayQueue
.queue_commit(server
);
702 if(deleted_rows
> 0) {
703 selection
.unselect_all();
708 // Show the Information of the first selected song.
709 private bool selected_songs_info()
711 var selection
= this
.get_selection();
713 foreach(var path
in selection
.get_selected_rows(out model
))
716 if(model
.get_iter(out iter
, path
))
718 unowned MPD
.Song? song
= null;
719 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.MPDSONG
, out song
);
721 Browser
.Metadata
.show();
722 Browser
.Metadata
.show_song(song
);
727 return selection
.count_selected_rows() > 0;
730 // Cut the selected songs in the play queue
731 private bool selected_songs_paste_queue_cut()
733 if(selected_songs_paste_queue_copy()){
734 selected_songs_remove();
738 // Copy the selected songs in the play queue
739 private bool selected_songs_paste_queue_copy()
741 var selection
= this
.get_selection();
742 // If nothing to copy , do nothing.
743 if(selection
.count_selected_rows() == 0) return false;
747 foreach(var path
in selection
.get_selected_rows(out model
))
750 if(model
.get_iter(out iter
, path
))
753 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.PATH
, out insert_path
);
754 paste_queue
.prepend(insert_path
);
757 paste_queue
.reverse();
762 private bool selected_songs_paste_before()
764 var selection
= this
.get_selection();
765 // If nothing selected.
766 if(selection
.count_selected_rows() == 0|| paste_queue
== null)
772 Gtk
.TreePath path
= selection
.get_selected_rows(out model
).last().data
;
773 if(model
.get_iter(out iter
, path
))
776 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_POS
, out songpos
);
779 paste_queue
.reverse();
780 foreach(var fpath
in paste_queue
)
782 int nsongid
= MPD
.PlayQueue
.add_song_get_id(server
,fpath
);
783 MPD
.PlayQueue
.song_move_id(server
, nsongid
, songpos
);
785 paste_queue
.reverse();
790 private bool selected_songs_paste_after()
792 var selection
= this
.get_selection();
793 // If nothing selected.
794 if(selection
.count_selected_rows() == 0|| paste_queue
== null)
800 Gtk
.TreePath path
= selection
.get_selected_rows(out model
).last().data
;
801 if(model
.get_iter(out iter
, path
))
804 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_POS
, out songpos
);
806 paste_queue
.reverse();
807 foreach(var fpath
in paste_queue
)
809 int nsongid
= MPD
.PlayQueue
.add_song_get_id(server
,fpath
);
810 MPD
.PlayQueue
.song_move_id(server
, nsongid
, songpos
);
812 paste_queue
.reverse();
821 private void move_cursor_down()
824 Gtk
.TreeViewColumn? col
;
825 this
.get_cursor(out path
, out col
);
830 if(this
.model
.get_iter(out iter
, path
))
832 this
.set_cursor(path
, col
, false);
836 private void move_cursor_up()
839 Gtk
.TreeViewColumn? col
;
840 this
.get_cursor(out path
, out col
);
845 this
.set_cursor(path
, col
, false);