Model parameter

Let's talk more about this second parameter. It could be used to send data to the widget when we create it. Remember when we called run():

App::run(()).unwrap();

Here, we specified () as the model parameter because we don't need one. But we could have used a different value, such as 42, and this value would have been received in the second parameter of the model() method.

We're now ready to create the view:

use gtk;
use gtk::{TreeViewExt, WidgetExt};
use relm::Widget;
use relm_attributes::widget;

#[widget]
impl Widget for Playlist {
    // …

    view! {
        #[name="treeview"]
        gtk::TreeView {
            hexpand: true,
            model: &self.model.model,
            vexpand: true,
        }
    }
}

It is really simple: we give it a name and set both the hexpand and vexpand properties to true and we bind the model property with our ListStore.

Let's create an empty update() method for now:

#[widget]
impl Widget for Playlist {
    // …

    fn update(&mut self, event: Msg) {
    }
}

We'll see the Msg type later. We'll now add the columns exactly like we did it in  Chapter 5Creating a Music Player. Let's copy the following enumeration and constants:

use self::Visibility::*;

#[derive(PartialEq)]
enum Visibility {
    Invisible,
    Visible,
}

const THUMBNAIL_COLUMN: u32 = 0;
const TITLE_COLUMN: u32 = 1;
const ARTIST_COLUMN: u32 = 2;
const ALBUM_COLUMN: u32 = 3;
const GENRE_COLUMN: u32 = 4;
const YEAR_COLUMN: u32 = 5;
const TRACK_COLUMN: u32 = 6;
const PATH_COLUMN: u32 = 7;
const PIXBUF_COLUMN: u32 = 8;

And let's add new methods to the Paylist:

impl Playlist {
    fn add_pixbuf_column(&self, 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);
        }
        self.treeview.append_column(&view_column);

    }

    fn add_text_column(&self, 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);
        self.treeview.append_column(&view_column);
    }

    fn create_columns(&self) {
        self.add_pixbuf_column(THUMBNAIL_COLUMN as i32, Visible);
        self.add_text_column("Title", TITLE_COLUMN as i32);
        self.add_text_column("Artist", ARTIST_COLUMN as i32);
        self.add_text_column("Album", ALBUM_COLUMN as i32);
        self.add_text_column("Genre", GENRE_COLUMN as i32);
        self.add_text_column("Year", YEAR_COLUMN as i32);
        self.add_text_column("Track", TRACK_COLUMN as i32);
        self.add_pixbuf_column(PIXBUF_COLUMN as i32, Invisible);
    }
}

The difference from these functions in  Chapter 5Creating a Music Player is that here, we have direct access to the treeview as an attribute. This requires new import statements:

use gtk::{
    CellLayoutExt,
    CellRendererPixbuf,
    CellRendererText,
    TreeViewColumn,
    TreeViewColumnExt,
    TreeViewExt,
};

We'll now call the create_columns() method in the init_view() method:

#[widget]
impl Widget for Playlist {
    fn init_view(&mut self) {
        self.create_columns();
    }

    // …
}

Let's start interacting with the playlist. We'll create a method to add a song to the playlist:

use std::path::Path;

use gtk::{ListStoreExt, ListStoreExtManual, ToValue};
use id3::Tag;

impl Playlist {
    fn add(&self, path: &Path) {
        let filename =  
path.file_stem().unwrap_or_default().to_str().unwrap_or_default(); let row = self.model.model.append(); if let Ok(tag) = Tag::read_from_path(path) { let title = tag.title().unwrap_or(filename); let artist = tag.artist().unwrap_or("(no artist)"); let album = tag.album().unwrap_or("(no album)"); let genre = tag.genre().unwrap_or("(no genre)"); let year = tag.year().map(|year|
year.to_string()).unwrap_or("(no year)".to_string()); let track = tag.track().map(|track|
track.to_string()).unwrap_or("??".to_string()); let total_tracks =
tag.total_tracks().map(|total_tracks|
total_tracks.to_string()).unwrap_or("??".to_string()); let track_value = format!("{} / {}", track,
total_tracks); self.set_pixbuf(&row, &tag); self.model.model.set_value(&row, TITLE_COLUMN,
&title.to_value()); self.model.model.set_value(&row, ARTIST_COLUMN,
&artist.to_value()); self.model.model.set_value(&row, ALBUM_COLUMN,
&album.to_value()); self.model.model.set_value(&row, GENRE_COLUMN,
&genre.to_value()); self.model.model.set_value(&row, YEAR_COLUMN,
&year.to_value()); self.model.model.set_value(&row, TRACK_COLUMN,
&track_value.to_value()); } else { self.model.model.set_value(&row, TITLE_COLUMN,
&filename.to_value()); } let path = path.to_str().unwrap_or_default(); self.model.model.set_value(&row, PATH_COLUMN,
&path.to_value()); } }

This calls the set_pixbuf() method, so let's define it:

use gdk_pixbuf::{InterpType, PixbufLoader};
use gtk::TreeIter;

const INTERP_HYPER: InterpType = 3;

const IMAGE_SIZE: i32 = 256;
const THUMBNAIL_SIZE: i32 = 64;

fn set_pixbuf(&self, row: &TreeIter, tag: &Tag) {
    if let Some(picture) = tag.pictures().next() {
        let pixbuf_loader = PixbufLoader::new();
        pixbuf_loader.set_size(IMAGE_SIZE, IMAGE_SIZE);
        pixbuf_loader.loader_write(&picture.data).unwrap();
        if let Some(pixbuf) = pixbuf_loader.get_pixbuf() {
            let thumbnail = pixbuf.scale_simple(THUMBNAIL_SIZE, 
THUMBNAIL_SIZE, INTERP_HYPER).unwrap(); self.model.model.set_value(row, THUMBNAIL_COLUMN,
&thumbnail.to_value()); self.model.model.set_value(row, PIXBUF_COLUMN,
&pixbuf.to_value()); } pixbuf_loader.close().unwrap(); } }

It is very similar to the one created in  Chapter 5Creating a Music Player. This method will be called when we receive the AddSong(path) message, so let's now create our message type:

use std::path::PathBuf;

use self::Msg::*;

#[derive(Msg)]
pub enum Msg {
    AddSong(PathBuf),
    LoadSong(PathBuf),
    NextSong,
    PauseSong,
    PlaySong,
    PreviousSong,
    RemoveSong,
    SaveSong(PathBuf),
    SongStarted(Option<Pixbuf>),
    StopSong,
}

And let's modify the update() method accordingly:

   fn update(&mut self, event: Msg) {
      match event {
          AddSong(path) => self.add(&path),
          LoadSong(path) => (),
          NextSong => (),
          PauseSong => (),
          PlaySong => (),
          PreviousSong => (),
          RemoveSong => (),
          SaveSong(path) => (),
          SongStarted(_) => (),
          StopSong => (),
        }
    }

Here, we call the method add() when we receive the AddSong message. But where is this message emitted? Well, it will be emitted by the App type, when the user requests to open a file. It is time we go back to the main module and use this new relm widget.