5 const string log_domain
= "Gmpc.DataView";
7 /** The Default column width. */
8 const int default_column_width
= 200;
12 * * List of column ids to show.
13 * * Name of each column
14 * * Default set of enabled columns.
16 const int NUM_COLS
= 20;
17 const int[] gmpc_data_view_col_ids
= {
18 Gmpc
.MpdData
.ColumnTypes
.MARKUP
,
19 Gmpc
.MpdData
.ColumnTypes
.SONG_ARTIST
, /* album name */
20 Gmpc
.MpdData
.ColumnTypes
.SONG_ALBUM
, /* album name */
21 Gmpc
.MpdData
.ColumnTypes
.SONG_TITLE
, /* song title */
22 Gmpc
.MpdData
.ColumnTypes
.SONG_TITLEFILE
, /* song title */
23 Gmpc
.MpdData
.ColumnTypes
.SONG_GENRE
, /* song genre */
24 Gmpc
.MpdData
.ColumnTypes
.SONG_TRACK
, /* song track */
25 Gmpc
.MpdData
.ColumnTypes
.SONG_NAME
, /* stream name */
26 Gmpc
.MpdData
.ColumnTypes
.SONG_COMPOSER
, /* composer name */
27 Gmpc
.MpdData
.ColumnTypes
.SONG_PERFORMER
, /* performer */
28 Gmpc
.MpdData
.ColumnTypes
.SONG_DATE
, /* date */
29 Gmpc
.MpdData
.ColumnTypes
.SONG_LENGTH_FORMAT
, /* length formatted */
30 Gmpc
.MpdData
.ColumnTypes
.SONG_DISC
, /* disc */
31 Gmpc
.MpdData
.ColumnTypes
.SONG_COMMENT
, /* comment */
32 Gmpc
.MpdData
.ColumnTypes
.ICON_ID
, /* icon id */
33 Gmpc
.MpdData
.ColumnTypes
.SONG_POS
,
34 Gmpc
.MpdData
.ColumnTypes
.SONG_ALBUMARTIST
,
35 Gmpc
.MpdData
.ColumnTypes
.PATH_EXTENSION
, /* Extension */
36 Gmpc
.MpdData
.ColumnTypes
.PATH_DIRECTORY
, /* Directory */
37 Gmpc
.MpdData
.ColumnTypes
.SONG_PRIORITY
39 const string[] gmpc_data_view_col_names
= {
62 const bool[] gmpc_data_view_col_enabled
= {
86 const int[] gmpc_data_view_col_position
= {
109 public class Gmpc
.DataView
: Gtk
.TreeView
111 private Gtk
.TreeViewColumn
[] tree_columns
= new Gtk
.TreeViewColumn
[NUM_COLS
];
113 * If we are a play-queue we should treat the content.
114 * slightly different.
115 * e.g. add-replace will be play-crop
117 public bool is_play_queue
{get; set; default=false;}
120 * The name of the treeview.
121 * This is used to store the column layout.
123 public string uid
{get; set; default="default";}
127 * Construction function.
129 public DataView(string name
, bool play_queue
= false)
131 log(log_domain
, LogLevelFlags
.LEVEL_INFO
, "Constructing dataview: "+name
);
132 this
.is_play_queue
= play_queue
;
134 // Connect row activated signal.
135 this
.row_activated
.connect(__row_activated
);
136 this
.key_press_event
.connect(__key_press_event_callback
);
137 this
.button_press_event
.connect(__button_press_event_callback
);
138 this
.button_release_event
.connect(__button_release_event_callback
);
139 // When it getst he destroy signal.
140 this
.destroy
.connect(column_store_state
);
142 this
.set_rules_hint(true);
143 this
.set_enable_search(false);
145 this
.get_selection().set_mode(Gtk
.SelectionMode
.MULTIPLE
);
146 this
.set_fixed_height_mode(true);
160 public void right_mouse_menu(Gtk
.Menu menu
)
162 int selected_rows
= this
.get_selection().count_selected_rows();
163 if(selected_rows
== 1) {
164 var item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.STOCK_MEDIA_PLAY
,null);
165 item
.activate
.connect((source
)=>{
166 selected_songs_play();
170 item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.STOCK_INFO
, null);
171 item
.activate
.connect((source
)=>{
172 selected_songs_info();
179 // Add play if there is one selected row.
180 if(selected_rows
> 0) {
181 var item
= new Gtk
.ImageMenuItem
.from_stock(Gtk
.STOCK_REMOVE
,null);
182 item
.activate
.connect((source
)=>{
183 selected_songs_remove();
187 if(server
.check_command_allowed("prioid") == MPD
.Server
.Command
.ALLOWED
)
189 var item
= new Gtk
.MenuItem
.with_label(_("Queue"));
190 item
.activate
.connect((source
)=>{ selected_songs_raise_priority();});
193 item
= new Gtk
.MenuItem
.with_label(_("Dequeue"));
194 item
.activate
.connect((source
)=>{ selected_songs_remove_priority();});
203 * Internal functions.
207 * Store the position, visibility and width of the columns
209 private void column_store_state()
211 // Save the position of the columns
212 var columns
= get_columns();
214 foreach(var column
in columns
)
216 int col_index
= column
.get_data("index");
217 int width
= column
.get_width();
218 config
.set_int(uid
+"-colpos", gmpc_data_view_col_names
[col_index
], index
);
219 config
.set_bool(uid
+"-colshow", gmpc_data_view_col_names
[col_index
], column
.visible
);
220 // Only store width if bigger then 0.
222 config
.set_int(uid
+"-colsize",gmpc_data_view_col_names
[col_index
], width
);
227 // Hack to make vala not destroy the menu directly.
228 private Gtk
.Menu column_selection_menu
= null;
229 private void column_show_selection_menu()
231 column_selection_menu
= new Gtk
.Menu();
232 foreach(var col
in tree_columns
)
234 int index
= col
.get_data("index");
235 // Do not show the icon id in the selection list.
236 if(gmpc_data_view_col_ids
[index
] == MpdData
.ColumnTypes
.ICON_ID
) continue;
237 var item
= new Gtk
.CheckMenuItem
.with_label(FixGtk
.gettext(gmpc_data_view_col_names
[index
]));
239 item
.set_active(true);
241 // On activation toggle the state.
242 item
.activate
.connect((source
) => {
243 col
.visible
= (source as Gtk
.CheckMenuItem
).get_active();
245 column_selection_menu
.append(item
);
247 column_selection_menu
.show_all();
248 column_selection_menu
.popup(null, null, null, 0, Gtk
.get_current_event_time());
251 * Populate the treeview with the right columns.
252 * The treeview should have a name now.
254 private void column_populate()
256 for(int i
= 0; i
< NUM_COLS
; i
++)
258 Gtk
.TreeViewColumn col
= new Gtk
.TreeViewColumn();
259 col
.set_data("index", i
);
260 if(gmpc_data_view_col_ids
[i
] == Gmpc
.MpdData
.ColumnTypes
.ICON_ID
)
265 var renderer
= new Gtk
.CellRendererPixbuf();
266 renderer
.xalign
= 0.0f
;
268 col
.pack_start(renderer
, true);
269 col
.set_attributes(renderer
, "icon-name", Gmpc
.MpdData
.ColumnTypes
.ICON_ID
);
270 col
.set_resizable(false);
271 col
.set_fixed_width(20);
272 col
.clickable
= true;
273 // If the user clicks on the column, show dropdown allowing to enable/disable columns.
274 col
.clicked
.connect((source
) => {
275 column_show_selection_menu();
281 col
.set_title(FixGtk
.gettext(gmpc_data_view_col_names
[i
]));
282 var renderer
= new Gtk
.CellRendererText();
283 renderer
.ellipsize
= Pango
.EllipsizeMode
.END
;
286 renderer
.weight_set
= true;
287 renderer
.style_set
= true;
288 col
.set_cell_data_func(renderer
, highlight_row_current_song_playing
);
290 col
.pack_start(renderer
, true);
291 col
.set_attributes(renderer
, "text", gmpc_data_view_col_ids
[i
]);
292 col
.set_resizable(true);
294 int width
= config
.get_int_with_default(uid
+"-colsize",gmpc_data_view_col_names
[i
], default_column_width
);
295 // Do not set to size 0, then revert back to 200.
296 col
.set_fixed_width(width
>0?width
:default_column_width
);
298 col
.set_sizing(Gtk
.TreeViewColumnSizing
.FIXED
);
299 col
.set_reorderable(true);
302 int pos
= config
.get_int_with_default(uid
+"-colpos", gmpc_data_view_col_names
[i
], gmpc_data_view_col_position
[i
]);
303 this
.tree_columns
[pos
] = col
;
305 // Add the columns (in right order)
306 for(int i
= 0; i
< NUM_COLS
; i
++) {
307 int index
= this
.tree_columns
[i
].get_data("index");
308 this
.insert_column(this
.tree_columns
[i
], i
);
309 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
]));
318 * Function handles the row-activate signal.
320 private void __row_activated (Gtk
.TreePath path
, Gtk
.TreeViewColumn col
)
322 Gtk
.TreeModel? model
= this
.get_model();
326 if(!model
.get_iter(out iter
, path
)) return;
328 /* If we are play-queue, play the selected song. */
330 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
331 MPD
.Player
.play_id(Gmpc
.server
, song_id
);
333 /* If we are a song browser, add the path and play it. */
335 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
336 MpdInteraction
.play_path(song_path
);
341 * Check if current row is playing.
343 private void highlight_row_current_song_playing(
345 Gtk
.CellRenderer renderer
,
349 // The current song we make bold.
350 if(model is Gmpc
.MpdData
.ModelPlaylist
&&
351 (model as Gmpc
.MpdData
.ModelPlaylist
).is_current_song(iter
)){
352 (renderer as Gtk
.CellRendererText
).weight
= Pango
.Weight
.BOLD
;
354 (renderer as Gtk
.CellRendererText
).weight
= Pango
.Weight
.NORMAL
;
357 // A prioritized song we make italic.
359 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_PRIORITY
, out prio
);
361 (renderer as Gtk
.CellRendererText
).style
= Pango
.Style
.ITALIC
;
363 (renderer as Gtk
.CellRendererText
).style
= Pango
.Style
.NORMAL
;
369 * Handle keyboard input.
371 private bool __key_press_event_callback_play_queue(Gdk
.EventKey event
)
373 if (event
.keyval
== Gdk
.Key_Q
)
376 return selected_songs_remove_priority();
378 else if (event
.keyval
== Gdk
.Key_q
)
381 return selected_songs_raise_priority();
383 else if (event
.keyval
== Gdk
.Key_d
)
385 if(!selected_songs_remove())
387 // Detach model (for some reason keeping it attached
388 // Makes thing break, work-around for now)
390 var model
= get_model();
393 MPD
.PlayQueue
.clear(server
);
401 private bool __key_press_event_callback(Gdk
.EventKey event
)
403 if(event
.keyval
== Gdk
.Key_j
)
408 else if (event
.keyval
== Gdk
.Key_k
)
413 else if(event
.keyval
== Gdk
.Key_y
)
415 // Copy data to clipboard
418 else if (event
.keyval
== Gdk
.Key_c
)
420 // Cut (if available) into clipboard
422 else if (event
.keyval
== Gdk
.Key_P
)
426 else if (event
.keyval
== Gdk
.Key_p
)
430 else if (event
.keyval
== Gdk
.Key_Escape
)
434 else if (event
.keyval
== Gdk
.Key_m
)
437 column_show_selection_menu();
440 else if (event
.keyval
== Gdk
.Key_Menu
)
442 __button_press_menu
= new Gtk
.Menu();
443 right_mouse_menu(__button_press_menu
);
444 __button_press_menu
.show_all();
445 __button_press_menu
.popup(null, null,null,0, Gtk
.get_current_event_time());
449 // Commands specific to play_queue
452 if(__key_press_event_callback_play_queue(event
)) return true;
456 if(event
.keyval
== Gdk
.Key_i
)
467 // Hack to stop vala from destroying my menu.
468 private Gtk
.Menu __button_press_menu
= null;
469 private bool __button_press_event_callback(Gdk
.EventButton event
)
471 if(event
.button
== 3) return true;
474 private bool __button_release_event_callback(Gdk
.EventButton event
)
476 if(event
.button
== 3)
478 __button_press_menu
= new Gtk
.Menu();
479 right_mouse_menu(__button_press_menu
);
480 __button_press_menu
.show_all();
481 __button_press_menu
.popup(null, null,null, event
.button
, event
.time
);
489 * Interaction on selected songs.
491 // Set priority on the selected songs.
492 private bool selected_songs_raise_priority()
494 if(server
.check_command_allowed("prioid") != MPD
.Server
.Command
.ALLOWED
) return false;
495 var selection
= this
.get_selection();
497 if(selection
.count_selected_rows() > 254) {
498 Gmpc
.Messages
.show(_("You can only queue 254 songs at the time."), Gmpc
.Messages
.Level
.WARNING
);
504 foreach(var path
in selection
.get_selected_rows(out model
))
507 if(model
.get_iter(out iter
, path
))
510 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
511 MPD
.PlayQueue
.set_priority(server
, song_id
, priority
--);
516 // Remove the set priority from the selected songs.
517 private bool selected_songs_remove_priority()
519 if(server
.check_command_allowed("prioid") != MPD
.Server
.Command
.ALLOWED
) return false;
520 var selection
= this
.get_selection();
523 foreach(var path
in selection
.get_selected_rows(out model
))
526 if(model
.get_iter(out iter
, path
))
529 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
530 MPD
.PlayQueue
.set_priority(server
, song_id
, 0);
535 // Play the selected song
536 private bool selected_songs_play()
538 var selection
= this
.get_selection();
540 foreach(var path
in selection
.get_selected_rows(out model
))
543 if(model
.get_iter(out iter
, path
))
547 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
549 MPD
.Player
.play_id(server
, song_id
);
554 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.PATH
, out song_path
);
555 MpdInteraction
.play_path(song_path
);
560 return selection
.count_selected_rows() > 0;
562 // Remove the selected songs from the play queue.
563 private bool selected_songs_remove()
565 int deleted_rows
= 0;
566 var selection
= this
.get_selection();
569 foreach(var path
in selection
.get_selected_rows(out model
))
572 if(model
.get_iter(out iter
, path
))
575 model
.get(iter
,Gmpc
.MpdData
.ColumnTypes
.SONG_ID
, out song_id
);
576 MPD
.PlayQueue
.queue_delete_id(server
, song_id
);
580 MPD
.PlayQueue
.queue_commit(server
);
581 return (deleted_rows
> 0);
584 private bool selected_songs_info()
586 var selection
= this
.get_selection();
588 foreach(var path
in selection
.get_selected_rows(out model
))
591 if(model
.get_iter(out iter
, path
))
593 MPD
.Song? song
= null;
594 model
.get(iter
, Gmpc
.MpdData
.ColumnTypes
.MPDSONG
, out song
);
596 Browser
.Metadata
.show();
597 Browser
.Metadata
.show_song(song
);
602 return selection
.count_selected_rows() > 0;
608 private void move_cursor_down()
611 Gtk
.TreeViewColumn? col
;
612 this
.get_cursor(out path
, out col
);
617 if(this
.model
.get_iter(out iter
, path
))
619 this
.set_cursor(path
, col
, false);
623 private void move_cursor_up()
626 Gtk
.TreeViewColumn? col
;
627 this
.get_cursor(out path
, out col
);
632 this
.set_cursor(path
, col
, false);