Hi everyone! In my first post, I want to talk about creating a simple radio in the Vala language. I have never been a programmer, but rather an amateur and I wrote all my developments in Java, but since I use GNU / Linux on my computer, namely the good old Debian, and even on GNOME, I thought, why not try to write something- anything for this system.
Training
No sooner said than done! I decided to write in the native text editor GEdit. Installed compiler and package for GTK development:
sudo apt install valac libgtk-3-dev
We also need the GStreamer developer package. Let's install it too:
sudo apt install libgstreamer1.0-dev
Move on! In the editor, you need to enable support for the built-in terminal and display the bottom panel, where the terminal will appear. Also, for convenience, you should include word completion, parentheses, and other functions. They are all in the list of modules. You can find it in the editor settings. Very handy stuff. In essence, they turn a regular text editor into a basic development environment.
Graphical interface
Files
So, we have an editor ready. Where to begin? From the interface, of course! We will write the GUI with pens in the editor. But first about the code itself. We will have two files. One is called Application.vala and has this content:
namespace Raddiola {
public class Application : Gtk.Application {
public MainWindow app_window;
public Application() {
Object(flags: ApplicationFlags.FLAGS_NONE, application_id: "com.github.alexkdeveloper.raddiola");
}
protected override void activate() {
if(get_windows().length() > 0) {
app_window.present();
return;
}
app_window = new MainWindow(this);
app_window.show_all();
}
public static int main(string[] args) {
Gst.init (ref args);
var app = new Raddiola.Application();
return app.run(args);
}
}
}
. MainWindow.vala .
, . , .
, "play" "stop", . . :
, . :
, :
using Gtk;
using Gst;
namespace Raddiola {
public class MainWindow : Gtk.ApplicationWindow {
private Stack stack;
private Box vbox_player_page;
private Box vbox_edit_page;
private dynamic Element player;
private Gtk.ListStore list_store;
private TreeView tree_view;
private GLib.List<string> list;
private Entry entry_name;
private Entry entry_url;
private Button back_button;
private Button add_button;
private Button delete_button;
private Button edit_button;
private Button play_button;
private Button stop_button;
private string directory_path;
private string item;
private int mode;
public MainWindow(Gtk.Application application) {
GLib.Object(application: application,
title: "Raddiola",
window_position: WindowPosition.CENTER,
resizable: true,
height_request: 500,
width_request: 500,
border_width: 10);
}
construct {
Gtk.HeaderBar headerbar = new Gtk.HeaderBar();
headerbar.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT);
headerbar.show_close_button = true;
set_titlebar(headerbar);
back_button = new Gtk.Button ();
back_button.set_image (new Gtk.Image.from_icon_name ("go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
back_button.vexpand = false;
add_button = new Gtk.Button ();
add_button.set_image (new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
add_button.vexpand = false;
delete_button = new Gtk.Button ();
delete_button.set_image (new Gtk.Image.from_icon_name ("list-remove-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
delete_button.vexpand = false;
edit_button = new Gtk.Button ();
edit_button.set_image (new Gtk.Image.from_icon_name ("document-edit-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
edit_button.vexpand = false;
play_button = new Gtk.Button();
play_button.set_image (new Gtk.Image.from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
play_button.vexpand = false;
stop_button = new Gtk.Button();
stop_button.set_image (new Gtk.Image.from_icon_name ("media-playback-stop-symbolic", Gtk.IconSize.SMALL_TOOLBAR));
stop_button.vexpand = false;
. , .
back_button.set_tooltip_text("back");
add_button.set_tooltip_text("add station");
delete_button.set_tooltip_text("delete station");
edit_button.set_tooltip_text("edit station");
play_button.set_tooltip_text("play");
stop_button.set_tooltip_text("stop");
back_button.clicked.connect(on_back_clicked);
add_button.clicked.connect(on_add_clicked);
delete_button.clicked.connect(on_delete_dialog);
edit_button.clicked.connect(on_edit_clicked);
play_button.clicked.connect(on_play_station);
stop_button.clicked.connect(on_stop_station);
headerbar.pack_start(back_button);
headerbar.pack_start(add_button);
headerbar.pack_start(delete_button);
headerbar.pack_start(edit_button);
headerbar.pack_end(stop_button);
headerbar.pack_end(play_button);
, set_widget_visible
:
set_widget_visible(back_button,false);
set_widget_visible(stop_button,false);
:
private void set_widget_visible (Gtk.Widget widget, bool visible) {
widget.no_show_all = !visible;
widget.visible = visible;
}
, . . :
stack = new Stack();
stack.set_transition_duration (600);
stack.set_transition_type (StackTransitionType.SLIDE_LEFT_RIGHT);
add (stack);//
list_store = new Gtk.ListStore(Columns.N_COLUMNS, typeof(string));//
tree_view = new TreeView.with_model(list_store);
var text = new CellRendererText ();
var column = new TreeViewColumn ();
column.pack_start (text, true);
column.add_attribute (text, "markup", Columns.TEXT);
tree_view.append_column (column);
tree_view.set_headers_visible (false);
tree_view.cursor_changed.connect(on_select_item);
var scroll = new ScrolledWindow (null, null);
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
scroll.add (this.tree_view);
vbox_player_page = new Box(Orientation.VERTICAL,20);
vbox_player_page.pack_start(scroll,true,true,0);
stack.add(vbox_player_page);//
. . , URL. , .
entry_name = new Entry();
entry_name.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");
entry_name.icon_press.connect ((pos, event) => {
if (pos == Gtk.EntryIconPosition.SECONDARY) {
entry_name.set_text("");//
}
});
var label_name = new Label.with_mnemonic ("_Name:");
var hbox_name = new Box (Orientation.HORIZONTAL, 20);
hbox_name.pack_start (label_name, false, true, 0);
hbox_name.pack_start (entry_name, true, true, 0);
entry_url = new Entry();
entry_url.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");
entry_url.icon_press.connect ((pos, event) => {
if (pos == Gtk.EntryIconPosition.SECONDARY) {
entry_url.set_text("");// URL
}
});
var label_url = new Label.with_mnemonic ("_URL:");
var hbox_url = new Box (Orientation.HORIZONTAL, 20);
hbox_url.pack_start (label_url, false, true, 0);
hbox_url.pack_start (entry_url, true, true, 0);
var button_ok = new Button.with_label("OK");
button_ok.clicked.connect(on_ok_clicked);
vbox_edit_page = new Box(Orientation.VERTICAL,20);
vbox_edit_page.pack_start(hbox_name,false,true,0);
vbox_edit_page.pack_start(hbox_url,false,true,0);
vbox_edit_page.pack_start(button_ok,false,true,0);
stack.add(vbox_edit_page);
stack.visible_child = vbox_player_page;//
GUI . player,
c . . .
player = ElementFactory.make ("playbin", "play");//
directory_path = Environment.get_home_dir()+"/.stations_for_radio_app";
GLib.File file = GLib.File.new_for_path(directory_path);
if(!file.query_exists()){
try{
file.make_directory();//
}catch(Error e){
stderr.printf ("Error: %s\n", e.message);
}
create_default_stations();//
}
show_stations();//
}
create_default_stations.
. , URL. . – , – URL.
private void create_default_stations(){
string[] name_station = {"NonStopPlay","Classical Music","Fip Radio","Jazz Legends","Joy Radio","Live-icy","Music Radio","Radio Electron","Dubstep","Trancemission"};
string[] url_station = {"http://stream.nonstopplay.co.uk/nsp-128k-mp3","http://stream.srg-ssr.ch/m/rsc_de/mp3_128","http://direct.fipradio.fr/live/fip-midfi.mp3","http://jazz128legends.streamr.ru/","http://airtime.joyradio.cc:8000/airtime_192.mp3","http://live-icy.gss.dr.dk:8000/A/A05H.mp3","http://ice-the.musicradio.com/CapitalXTRANationalMP3","http://radio-electron.ru:8000/128","http://air.radiorecord.ru:8102/dub_320","http://air.radiorecord.ru:8102/tm_320"};
for(int i=0;i<10;i++){
try {
FileUtils.set_contents (directory_path+"/"+name_station[i], url_station[i]);
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
}
}
show_stations:
private void show_stations () {
list_store.clear();
list = new GLib.List<string> ();
try {
Dir dir = Dir.open (directory_path, 0);
string? name = null;
while ((name = dir.read_name ()) != null) {
list.append(name);
}
} catch (FileError err) {
stderr.printf (err.message);
}
TreeIter iter;
foreach (string item in list) {
list_store.append(out iter);
list_store.set(iter, Columns.TEXT, item);//
}
}
/
alert.
:
private void alert (string str){
var dialog_alert = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, str);
dialog_alert.set_title("Message");
dialog_alert.run();
dialog_alert.destroy();
}
:
private void on_play_station(){ //
var selection = tree_view.get_selection();
selection.set_mode(SelectionMode.SINGLE);
TreeModel model;
TreeIter iter;
if (!selection.get_selected(out model, out iter)) {
alert("Choose a station");
return;
}
string uri;
try {
FileUtils.get_contents (directory_path+"/"+item, out uri);
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
player.uri = uri;
player.set_state (State.PLAYING);
set_widget_visible(play_button,false);
set_widget_visible(stop_button,true);
}
private void on_stop_station(){//
player.set_state (State.READY);
set_widget_visible(play_button,true);
set_widget_visible(stop_button,false);
}
:
private void on_add_clicked () {//
stack.visible_child = vbox_edit_page;
set_buttons_on_edit_page();
mode = 1;
if(!is_empty(entry_name.get_text())){
entry_name.set_text("");
}
if(!is_empty(entry_url.get_text())){
entry_url.set_text("");
}
}
private void on_edit_clicked(){//
var selection = tree_view.get_selection();
selection.set_mode(SelectionMode.SINGLE);
TreeModel model;
TreeIter iter;
if (!selection.get_selected(out model, out iter)) {
alert("Choose a station");
return;
}
stack.visible_child = vbox_edit_page;
set_buttons_on_edit_page();
mode = 0;
entry_name.set_text(item);//
string url;
try {
FileUtils.get_contents (directory_path+"/"+item, out url);
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
entry_url.set_text(url);// URL
}
. mode
. : . .
private void on_ok_clicked(){
if(is_empty(entry_name.get_text())){
alert("Enter the name");
entry_name.grab_focus();
return;
}
if(is_empty(entry_url.get_text())){
alert("Enter the url");
entry_url.grab_focus();
return;
}
switch(mode){//
case 0://
GLib.File select_file = GLib.File.new_for_path(directory_path+"/"+item);
GLib.File edit_file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());
if (select_file.get_basename() != edit_file.get_basename() && !edit_file.query_exists()){
FileUtils.rename(select_file.get_path(), edit_file.get_path());
if(!edit_file.query_exists()){
alert("Rename failed");
return;
}
try {
FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
}else{
if (select_file.get_basename() != edit_file.get_basename()) {
alert("A station with the same name already exists");
entry_name.grab_focus();
return;
}
try {
FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
}
show_stations();
break;
case 1://
GLib.File file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());
if(file.query_exists()){
alert("A station with the same name already exists");
entry_name.grab_focus();
return;
}
try {
FileUtils.set_contents (file.get_path(), entry_url.get_text().strip());
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
if(!file.query_exists()){
alert("Add failed");
return;
}else{
show_stations();
}
break;
}
on_back_clicked();
}
:
private void on_delete_dialog(){
var selection = tree_view.get_selection();
selection.set_mode(SelectionMode.SINGLE);
TreeModel model;
TreeIter iter;
if (!selection.get_selected(out model, out iter)) {
alert("Choose a station");
return;
}
GLib.File file = GLib.File.new_for_path(directory_path+"/"+item);
var dialog_delete_station = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Delete station "+file.get_basename()+" ?");
dialog_delete_station.set_title("Question");
Gtk.ResponseType result = (ResponseType)dialog_delete_station.run ();
dialog_delete_station.destroy();
if(result==Gtk.ResponseType.OK){
FileUtils.remove (directory_path+"/"+item);
if(file.query_exists()){
alert("Delete failed");
}else{
show_stations();//
}
}
}
:
private void set_buttons_on_player_page(){//
set_widget_visible(back_button,false);
set_widget_visible(add_button,true);
set_widget_visible(delete_button,true);
set_widget_visible(edit_button,true);
}
private void set_buttons_on_edit_page(){// /
set_widget_visible(back_button,true);
set_widget_visible(add_button,false);
set_widget_visible(delete_button,false);
set_widget_visible(edit_button,false);
}
item
. , - :
private void on_select_item () {
var selection = tree_view.get_selection();
selection.set_mode(SelectionMode.SINGLE);
TreeModel model;
TreeIter iter;
if (!selection.get_selected(out model, out iter)) {
return;
}
TreePath path = model.get_path(iter);
var index = int.parse(path.to_string());
if (index >= 0) {
item = list.nth_data(index);//
}
}
:
private void on_back_clicked(){
stack.visible_child = vbox_player_page;
set_buttons_on_player_page();
}
is_empty:
private bool is_empty(string str){
return str.strip().length == 0;
}
:
private enum Columns {
TEXT, N_COLUMNS
}
:
valac --pkg gtk+-3.0 --pkg gstreamer-1.0 Application.vala MainWindow.vala
:
./Application
→ GitHub
! , .