For the list and tree widgets, GTK+ follows the MVC pattern. MVC stands for Model-View-Controller.
Now we can add a constructor for our playlist:
impl Playlist { pub fn new() -> Self { let model = ListStore::new(&[ Pixbuf::static_type(), Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Pixbuf::static_type(), ]); let treeview = TreeView::new_with_model(&model); treeview.set_hexpand(true); treeview.set_vexpand(true); Self::create_columns(&treeview); Playlist { model, treeview, } } }
The gtk::ListStore type is a model to represent the data as a list. Its constructor needs the types of the columns; in this case, most of the types are strings for the metadata of the MP3 files, such as the song title and author name. The first Pixbuf is for the thumbnail image and the last one is for the bigger image only shown for the music currently playing.
Next, we create a TreeView, which will actually be a view for a list since we initialize it with our list model. We then modify the widget so that it expands both vertically and horizontally, meaning that the widget will use as much space as possible. Finally, just before we return the struct Playlist, we call the create_columns() method, which will create the columns to be shown in this view. Let's see this new method:
fn create_columns(treeview: &TreeView) { Self::add_pixbuf_column(treeview, THUMBNAIL_COLUMN as i32,
Visible); Self::add_text_column(treeview, "Title", TITLE_COLUMN as i32); Self::add_text_column(treeview, "Artist", ARTIST_COLUMN as i32); Self::add_text_column(treeview, "Album", ALBUM_COLUMN as i32); Self::add_text_column(treeview, "Genre", GENRE_COLUMN as i32); Self::add_text_column(treeview, "Year", YEAR_COLUMN as i32); Self::add_text_column(treeview, "Track", TRACK_COLUMN as i32); Self::add_pixbuf_column(treeview, PIXBUF_COLUMN as i32, Invisible); }
Here, we call two methods to create the different types of columns—we specify the header label and the column number of every column. As for the last parameter of the add_pixbuf_column() method, it indicates whether the column is visible or not. This parameter is of a custom type, so let's declare it:
use self::Visibility::*; #[derive(PartialEq)] enum Visibility { Invisible, Visible, }
We also added a use statement to be able to directly use Visible instead of having to fully qualify it (Visibility::Visible).
Let's write the add_text_column() method:
fn add_text_column(treeview: &TreeView, title: &str, column: i32) { let view_column = TreeViewColumn::new(); view_column.set_title(title); let cell = CellRendererText::new(); view_column.set_expand(true); view_column.pack_start(&cell, true); view_column.add_attribute(&cell, "text", column); treeview.append_column(&view_column); }
We start by creating the column itself and setting the label of the header by calling set_title(). Then, we create a CellRenderer, which indicates how the data from the model should be rendered in the view; here, we only want to show some text, so we chose CellRendererText, we set it to take up as much space as possible, and added the renderer to the column. Next comes a very important line:
view_column.add_attribute(&cell, "text", column);
This line specifies that the view will set the text attribute from the data that comes from the model at the specified column.
At the end, we add the column to the view.
Now we'll write a similar function for the pixbuf:
fn add_pixbuf_column(treeview: &TreeView, column: i32, visibility:
Visibility) { let view_column = TreeViewColumn::new(); if visibility == Visible { let cell = CellRendererPixbuf::new(); view_column.pack_start(&cell, true); view_column.add_attribute(&cell, "pixbuf", column); } treeview.append_column(&view_column); }
Here, we create a new type of renderer (CellRendererPixbuf), which will show an image instead of text. This time, we set the pixbuf attribute because we want to show an image. The renderer is only created if the column is visible.
Now, all that's left is to write a function to get the actual widget to be able to add the widget in the main module:
pub fn view(&self) -> &TreeView { &self.treeview }
Let's go back to the method App::new() and create the playlist:
let playlist = Playlist::new();
vbox.add(playlist.view());
(Add this code right before creating the Image.)
We'll also add a playlist attribute in the structure:
struct App { adjustment: Adjustment, cover: Image, playlist: Playlist, toolbar: MusicToolbar, window: Window, }
Also, don't forget to edit the creation of the structure to include the following new field:
let app = App { adjustment, cover, playlist, toolbar, window, };
We're now ready to launch our application again to see an empty playlist:
