[DataView] Fix translation + Add yet another binding fix for vala.
[gmpc.git] / src / Widgets / gmpc-data-view.vala
blob6a7627ccf8487b35580df2531bd42d0b97c0c207
1 using Gmpc;
3 using Gtk;
5 const string log_domain = "Gmpc.DataView";
7 /** The Default column width. */
8 const int default_column_width = 200;
10 /**
11 * List of columns.
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 = {
40 N_("Markup"),
41 N_("Artist"),
42 N_("Album"),
43 N_("Title"),
44 N_("File"),
45 N_("Genre"),
46 N_("Track"),
47 N_("Name"),
48 N_("Composer"),
49 N_("Performer"),
50 N_("Date"),
51 N_("Duration"),
52 N_("Disc"),
53 N_("Comment"),
54 N_("Icon Id"),
55 N_("Position"),
56 N_("AlbumArtist"),
57 N_("Extension"),
58 N_("Directory"),
59 N_("Priority")
62 const bool[] gmpc_data_view_col_enabled = {
63 false,//"Markup",
64 true, //"Artist",
65 true,//"Album",
66 true,//"Title",
67 false,//"File",
68 false,//"Genre",
69 false,//"Track",
70 false,//"Name",
71 false,//"Composer",
72 false,//"Performer",
73 false,//"Date",
74 false,//"Duration",
75 false,//"Disc",
76 false,//"Comment",
77 true,//"Icon Id"
78 false,//"Position"
79 false,//"AlbumArtist"
80 false,//Extension
81 false,//Directory
82 false//Priority
86 const int[] gmpc_data_view_col_position = {
87 14,//"Markup",
88 3, //"Artist",
89 2,//"Album",
90 1,//"Title",
91 4,//"File",
92 5,//"Genre",
93 6,//"Track",
94 7,//"Name",
95 8,//"Composer",
96 9,//"Performer",
97 10,//"Date",
98 11,//"Duration",
99 12,//"Disc",
100 13,//"Comment",
101 0,//"Icon Id"
102 15, // "Position"
103 18, // "AlbumArtist"
104 16,// Extension
105 17, // Directory
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;
133 this.uid = name;
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);
147 // Create the view.
148 column_populate();
153 * Deconstructor.
155 ~DataView()
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();
168 menu.append(item);
170 item = new Gtk.ImageMenuItem.from_stock(Gtk.STOCK_INFO, null);
171 item.activate.connect((source)=>{
172 selected_songs_info();
174 menu.append(item);
177 if(is_play_queue)
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();
185 menu.append(item);
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();});
191 menu.append(item);
193 item = new Gtk.MenuItem.with_label(_("Dequeue"));
194 item.activate.connect((source)=>{ selected_songs_remove_priority();});
195 menu.append(item);
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();
213 int index = 0;
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.
221 if(width > 0 ) {
222 config.set_int(uid+"-colsize",gmpc_data_view_col_names[col_index], width);
224 index++;
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]));
238 if(col.visible) {
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)
263 * Picture.
265 var renderer = new Gtk.CellRendererPixbuf();
266 renderer.xalign = 0.0f;
267 // Set up column
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();
277 } else {
279 * Text column
281 col.set_title(FixGtk.gettext(gmpc_data_view_col_names[i]));
282 var renderer = new Gtk.CellRendererText();
283 renderer.ellipsize = Pango.EllipsizeMode.END;
284 // Set up column
285 if(is_play_queue) {
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);
301 // Fixed width.
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();
323 if(model != null)
325 Gtk.TreeIter iter;
326 if(!model.get_iter(out iter, path)) return;
327 if(is_play_queue) {
328 /* If we are play-queue, play the selected song. */
329 int song_id;
330 model.get(iter, Gmpc.MpdData.ColumnTypes.SONG_ID, out song_id);
331 MPD.Player.play_id(Gmpc.server, song_id);
332 } else {
333 /* If we are a song browser, add the path and play it. */
334 string song_path;
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(
344 Gtk.CellLayout col,
345 Gtk.CellRenderer renderer,
346 Gtk.TreeModel model,
347 Gtk.TreeIter iter)
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;
353 }else{
354 (renderer as Gtk.CellRendererText).weight = Pango.Weight.NORMAL;
357 // A prioritized song we make italic.
358 int prio = 0;
359 model.get(iter, Gmpc.MpdData.ColumnTypes.SONG_PRIORITY, out prio);
360 if(prio > 0) {
361 (renderer as Gtk.CellRendererText).style = Pango.Style.ITALIC;
362 }else {
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)
375 // remove priority.
376 return selected_songs_remove_priority();
378 else if (event.keyval == Gdk.Key_q)
380 // Raise priority.
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)
389 // TODO: fixme
390 var model = get_model();
391 this.model = null;
392 // Clear
393 MPD.PlayQueue.clear(server);
394 // Re-add model
395 this.model = model;
396 return true;
399 return false;
401 private bool __key_press_event_callback(Gdk.EventKey event)
403 if(event.keyval == Gdk.Key_j)
405 // Move cursor down.
406 move_cursor_down();
408 else if (event.keyval == Gdk.Key_k)
410 // Move cursor up
411 move_cursor_up();
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)
424 // Paste before
426 else if (event.keyval == Gdk.Key_p)
428 // Paste after
430 else if (event.keyval == Gdk.Key_Escape)
434 else if (event.keyval == Gdk.Key_m)
436 // Configure columns
437 column_show_selection_menu();
438 return true;
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());
446 return true;
449 // Commands specific to play_queue
450 if(is_play_queue)
452 if(__key_press_event_callback_play_queue(event)) return true;
454 else
456 if(event.keyval == Gdk.Key_i)
458 // Insert
461 return false;
465 * Right mouse popup.
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;
472 return false;
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);
482 return true;
484 return false;
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);
499 return false;
502 int priority = 255;
503 Gtk.TreeModel model;
504 foreach(var path in selection.get_selected_rows(out model))
506 Gtk.TreeIter iter;
507 if(model.get_iter(out iter, path))
509 int song_id;
510 model.get(iter,Gmpc.MpdData.ColumnTypes.SONG_ID, out song_id);
511 MPD.PlayQueue.set_priority(server, song_id, priority--);
514 return true;
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();
522 Gtk.TreeModel model;
523 foreach(var path in selection.get_selected_rows(out model))
525 Gtk.TreeIter iter;
526 if(model.get_iter(out iter, path))
528 int song_id;
529 model.get(iter,Gmpc.MpdData.ColumnTypes.SONG_ID, out song_id);
530 MPD.PlayQueue.set_priority(server, song_id, 0);
533 return true;
535 // Play the selected song
536 private bool selected_songs_play()
538 var selection = this.get_selection();
539 Gtk.TreeModel model;
540 foreach(var path in selection.get_selected_rows(out model))
542 Gtk.TreeIter iter;
543 if(model.get_iter(out iter, path))
545 if(is_play_queue) {
546 int song_id;
547 model.get(iter, Gmpc.MpdData.ColumnTypes.SONG_ID, out song_id);
548 if(song_id >= 0){
549 MPD.Player.play_id(server, song_id);
550 return true;
552 }else{
553 string song_path;
554 model.get(iter, Gmpc.MpdData.ColumnTypes.PATH, out song_path);
555 MpdInteraction.play_path(song_path);
556 return true;
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();
568 Gtk.TreeModel model;
569 foreach(var path in selection.get_selected_rows(out model))
571 Gtk.TreeIter iter;
572 if(model.get_iter(out iter, path))
574 int song_id;
575 model.get(iter,Gmpc.MpdData.ColumnTypes.SONG_ID, out song_id);
576 MPD.PlayQueue.queue_delete_id(server, song_id);
577 deleted_rows++;
580 MPD.PlayQueue.queue_commit(server);
581 return (deleted_rows > 0);
584 private bool selected_songs_info()
586 var selection = this.get_selection();
587 Gtk.TreeModel model;
588 foreach(var path in selection.get_selected_rows(out model))
590 Gtk.TreeIter iter;
591 if(model.get_iter(out iter, path))
593 MPD.Song? song = null;
594 model.get(iter, Gmpc.MpdData.ColumnTypes.MPDSONG, out song);
595 if(song != null) {
596 Browser.Metadata.show();
597 Browser.Metadata.show_song(song);
598 return true;
602 return selection.count_selected_rows() > 0;
606 * Move
608 private void move_cursor_down()
610 Gtk.TreePath? path;
611 Gtk.TreeViewColumn? col;
612 this.get_cursor(out path, out col);
613 if(path != null)
615 Gtk.TreeIter iter;
616 path.next();
617 if(this.model.get_iter(out iter, path))
619 this.set_cursor(path, col, false);
623 private void move_cursor_up()
625 Gtk.TreePath? path;
626 Gtk.TreeViewColumn? col;
627 this.get_cursor(out path, out col);
628 if(path != null)
630 if(path.prev())
632 this.set_cursor(path, col, false);