This commit is contained in:
liyaowhen
2024-07-14 22:55:41 -07:00
commit d4e7133248
39 changed files with 2351 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
/* application.vala
*
* Copyright 2024 Leo Wen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Song {
public class Application : Adw.Application {
public Application () {
Object (application_id: "com.liyaowhen.Song", flags: ApplicationFlags.DEFAULT_FLAGS);
}
construct {
ActionEntry[] action_entries = {
{ "about", this.on_about_action },
{ "preferences", this.on_preferences_action },
{ "quit", this.quit }
};
this.add_action_entries (action_entries, this);
this.set_accels_for_action ("app.quit", {"<primary>q"});
}
public override void activate () {
base.activate ();
var win = this.active_window;
if (win == null) {
win = new Song.Window (this);
}
win.present ();
}
private void on_about_action () {
string[] developers = { "Leo Wen" };
var about = new Adw.AboutWindow () {
transient_for = this.active_window,
application_name = "song",
application_icon = "com.liyaowhen.Song",
developer_name = "Leo Wen",
version = "0.1.0",
developers = developers,
copyright = "© 2024 Leo Wen",
};
about.present ();
}
private void on_preferences_action () {
message ("app.preferences action activated");
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using Gtk;
namespace Song {
public delegate void EventQueue();
/* to ensure that doing things on the main_view_content or sidebar_content
in this SongController allows for those objects to exist, instead of
doing functions related to this controller in the bare construct {},
put it in a signal that will be released when the SongController is ready to use */
public class SongController : Object {
public static MainViewContent main_view_content; // main view content should avoid using this controller during instanciation
public static SideBarContent sidebar_content;
public static SongControls song_controls;
public static GestureDrag gesture;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkShortcutsWindow" id="help_overlay">
<property name="modal">True</property>
<child>
<object class="GtkShortcutsSection">
<property name="section-name">shortcuts</property>
<property name="max-height">10</property>
<child>
<object class="GtkShortcutsGroup">
<property name="title" translatable="yes" context="shortcut window">General</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes" context="shortcut window">Show Shortcuts</property>
<property name="action-name">win.show-help-overlay</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes" context="shortcut window">Quit</property>
<property name="action-name">app.quit</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 5 0 c -1.644531 0 -3 1.355469 -3 3 v 2 h 2 v -2 c 0 -0.570312 0.429688 -1 1 -1 h 8 c 0.570312 0 1 0.429688 1 1 v 10 c 0 0.570312 -0.429688 1 -1 1 h -8 c -0.570312 0 -1 -0.429688 -1 -1 v -2 h -2 v 2 c 0 1.644531 1.355469 3 3 3 h 8 c 1.644531 0 3 -1.355469 3 -3 v -10 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 0"/><path d="m 6 4 v 1 c 0 0.257812 0.128906 0.527344 0.3125 0.71875 l 1.28125 1.28125 h -7.59375 v 2 h 7.59375 l -1.28125 1.28125 c -0.183594 0.191406 -0.3125 0.410156 -0.3125 0.71875 v 1 h 1 c 0.308594 0 0.550781 -0.089844 0.75 -0.28125 l 3.65625 -3.71875 l -3.65625 -3.71875 c -0.199219 -0.191406 -0.441406 -0.28125 -0.75 -0.28125 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 815 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.5 0 c -4.128906 0 -7.5 3.371094 -7.5 7.5 s 3.371094 7.5 7.5 7.5 s 7.5 -3.371094 7.5 -7.5 s -3.371094 -7.5 -7.5 -7.5 z m 1.84375 2.3125 c 0.953125 0.335938 1.78125 0.925781 2.40625 1.6875 h -1.226562 c -0.304688 -0.609375 -0.699219 -1.179688 -1.179688 -1.6875 z m -3.71875 0.011719 c -0.472656 0.507812 -0.867188 1.070312 -1.171875 1.675781 h -1.203125 c 0.617188 -0.753906 1.4375 -1.335938 2.375 -1.675781 z m 1.375 0.097656 v 1.578125 h -1.398438 c 0.363282 -0.59375 0.828126 -1.128906 1.398438 -1.578125 z m 1 0.015625 c 0.558594 0.445312 1.019531 0.976562 1.378906 1.5625 h -1.378906 z m -5.402344 2.5625 h 1.441406 c -0.214843 0.644531 -0.347656 1.320312 -0.390624 2 h -1.625 c 0.0625 -0.714844 0.265624 -1.390625 0.574218 -2 z m 2.507813 0 h 1.894531 v 2 h -2.34375 c 0.046875 -0.683594 0.195312 -1.359375 0.449219 -2 z m 2.894531 0 h 1.875 c 0.253906 0.640625 0.402344 1.316406 0.449219 2 h -2.324219 z m 2.941406 0 h 1.460938 c 0.308594 0.609375 0.511718 1.285156 0.574218 2 h -1.644531 c -0.042969 -0.679688 -0.175781 -1.355469 -0.390625 -2 z m -8.917968 3 h 1.625 c 0.046874 0.679688 0.175781 1.355469 0.394531 2 h -1.445313 c -0.308594 -0.609375 -0.511718 -1.285156 -0.574218 -2 z m 2.632812 0 h 2.34375 v 2 h -1.890625 c -0.253906 -0.636719 -0.402344 -1.316406 -0.453125 -2 z m 3.34375 0 h 2.324219 c -0.050781 0.683594 -0.199219 1.363281 -0.453125 2 h -1.871094 z m 3.332031 0 h 1.644531 c -0.0625 0.714844 -0.265624 1.390625 -0.574218 2 h -1.464844 c 0.21875 -0.644531 0.347656 -1.320312 0.394531 -2 z m -8.082031 3 h 1.210938 c 0.3125 0.609375 0.710937 1.179688 1.195312 1.6875 c -0.953125 -0.335938 -1.78125 -0.925781 -2.40625 -1.6875 z m 2.363281 0 h 1.386719 v 1.550781 c -0.5625 -0.441406 -1.027344 -0.96875 -1.386719 -1.550781 z m 2.386719 0 h 1.367188 c -0.355469 0.578125 -0.8125 1.097656 -1.367188 1.535156 z m 2.519531 0 h 1.230469 c -0.632812 0.769531 -1.472656 1.363281 -2.4375 1.695312 c 0.492188 -0.507812 0.894531 -1.082031 1.207031 -1.695312 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+22
View File
@@ -0,0 +1,22 @@
using Json;
namespace Song {
public class JsonConfig : GLib.Object {
private Settings settings = new Settings("com.liyaowhen.Song");
public class JsonConfig () {
var json_text = settings.get_string("songs");
var parser = new Json.Parser();
try {
parser.load_from_data (json_text, json_text.length);
} catch (GLib.Error e) {
print(e.message);
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/* main.vala
*
* Copyright 2024 Leo Wen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
int main (string[] args) {
Gst.init(ref args);
var app = new Song.Application ();
return app.run (args);
}
+32
View File
@@ -0,0 +1,32 @@
song_sources = [
'main.vala',
'application.vala',
'window.vala',
'music/new_music_popover.vala',
'widgets/side_bar.vala',
'json_config.vala',
'widgets/main_view.vala',
'widgets/playlist.vala',
'controllers/song_controller.vala',
'widgets/music_list_row.vala',
'widgets/song_controls.vala'
]
song_deps = [
dependency('gtk4'),
dependency('libadwaita-1', version: '>= 1.4'),
dependency('vte-2.91-gtk4'),
dependency('jsonrpc-glib-1.0'),
dependency('gstreamer-1.0')
]
song_sources += gnome.compile_resources('song-resources',
'song.gresource.xml',
c_name: 'song'
)
executable('song', song_sources,
dependencies: song_deps,
install: true,
)
+311
View File
@@ -0,0 +1,311 @@
using GLib;
namespace Song {
public class NewMusicPopover : Adw.Dialog {
private Settings settings = new Settings ("com.liyaowhen.Song");
public signal void download();
private Adw.NavigationView navigation_view_steps;
private string steps_state = "";
private Gtk.Button next_button;
private bool isPlaylistFolder;
public string song_title = "";
public string song_url = "";
construct {
var header = new Adw.HeaderBar ();
header.show_end_title_buttons = false;
header.show_start_title_buttons = false;
provide_ui();
var cancel_button = new Gtk.Button();
cancel_button.label = "Cancel";
cancel_button.clicked.connect(() => {
close();
});
next_button = new Gtk.Button();
next_button.label = "Next";
next_button.hide();
next_button.clicked.connect(() => {
if (steps_state == "online") {
var good = false;
string command = "ping " + song_url; // Replace with your desired Linux command
string standard_output;
string standard_error;
int wait_status;
download();
/*try {
Process.spawn_command_line_sync(command,
out standard_output,
out standard_error,
out wait_status);
print ("stdout:\n");
// Output: ````
print (standard_output);
print ("stderr:\n");
print (standard_error);
// Output: ``0``
print ("Status: %d\n", wait_status);
download();
} catch (SpawnError e) {
print ("Error: %s\n", e.message);
} */
}
});
header.pack_end(next_button);
header.pack_start(cancel_button);
var action_bar = new Gtk.ActionBar ();
var content = new Adw.ToolbarView ();
content.add_top_bar (header);
content.set_content(navigation_view_steps);
//content.add_bottom_bar (action_bar);
set_child(content);
set_title("create new song");
follows_content_size = true;
}
private void provide_ui() {
navigation_view_steps = new Adw.NavigationView();
// starting page (choose what kind of app should be)
var song_folder_button = new Gtk.Button();
var song_folder_button_content = new Adw.ButtonContent();
song_folder_button_content.set_icon_name("folder-symbolic");
song_folder_button_content.set_parent(song_folder_button);
song_folder_button.set_hexpand(false);
song_folder_button.clicked.connect(() => {
var file_chooser = new Gtk.FileChooserDialog("Choose Folder",
null,
Gtk.FileChooserAction.SELECT_FOLDER,
"_Open",
Gtk.ResponseType.ACCEPT,
"_Cancel",
Gtk.ResponseType.CANCEL);
file_chooser.response.connect((dialouge, response) => {
if (response == Gtk.ResponseType.ACCEPT) {
// ... retrieve the location from the dialog and open it
settings.set_string("song-folder", file_chooser.get_file().get_path());
print(settings.get_string("song-folder"));
file_chooser.destroy();
isPlaylistFolder = true;
}
if (response == Gtk.ResponseType.CANCEL) {
file_chooser.destroy();
}
});
file_chooser.show();
});
if (settings.get_string("song-folder") == null) {
isPlaylistFolder = false;
} else isPlaylistFolder = true;
song_folder_button.destroy.connect(() => {
// for safety
song_folder_button_content.destroy();
});
var online_button = new Gtk.Button();
var online_button_content = new Adw.ButtonContent();
online_button_content.set_icon_name("globe-alt2-symbolic");
online_button_content.set_label("from the internet (spotify and youtube supported)");
online_button_content.set_parent(online_button);
online_button.set_hexpand(false);
online_button.sensitive = isPlaylistFolder;
online_button.destroy.connect(() => {
// for safety
online_button_content.destroy();
});
var import_button = new Gtk.Button();
var import_button_content = new Adw.ButtonContent();
import_button_content.set_icon_name("arrow-into-box-symbolic");
import_button_content.set_label("use your own music");
import_button_content.set_parent(import_button);
import_button.set_hexpand(false);
import_button.sensitive = isPlaylistFolder;
import_button.destroy.connect(() => {
// for safety
import_button_content.destroy();
});
var starting_page_content = new Gtk.Box(Gtk.Orientation.VERTICAL, 5);
starting_page_content.append(song_folder_button);
starting_page_content.append(online_button);
starting_page_content.append(import_button);
var starting_page = new Adw.NavigationPage(starting_page_content, "main");
//add from online
var title_entry = new Adw.EntryRow();
title_entry.title = "Title";
var url_entry = new Adw.EntryRow();
url_entry.title = "link to song (youtube, spotify supported)";
title_entry.changed.connect(() => {
song_title = title_entry.text;
if (song_url.length != 0 && song_title.length != 0) {
next_button.sensitive = true;
} else {
next_button.sensitive = false;
}
});
url_entry.changed.connect(() => {
song_url = url_entry.text;
if (song_url.length != 0 && song_title.length != 0) {
next_button.sensitive = true;
} else {
next_button.sensitive = false;
}
});
var online_page_content = new Gtk.ListBox();
online_page_content.append(title_entry);
online_page_content.append(url_entry);
var online_page = new Adw.NavigationPage(online_page_content, "beginning");
//downloading page
var terminal = new Vte.Terminal();
var downloading_page = new Adw.NavigationPage(terminal, "Downloading...");
// complete page
var complete_page_widget = new Adw.StatusPage();
complete_page_widget.set_icon_name("emoji-body-symbolic");
complete_page_widget.set_title("Done importing song");
complete_page_widget.set_description("go back to listening to songs");
complete_page_widget.add_css_class("compact");
var complete_page = new Adw.NavigationPage(complete_page_widget, "Done");
//show the views
navigation_view_steps.add(starting_page);
navigation_view_steps.add(online_page);
navigation_view_steps.add(downloading_page);
navigation_view_steps.add(complete_page);
navigation_view_steps.margin_bottom = 5;
navigation_view_steps.margin_top = 5;
navigation_view_steps.margin_start = 5;
navigation_view_steps.margin_end = 5;
// handle moving around the navigation view
online_button.clicked.connect(() => {
navigation_view_steps.push(online_page);
next_button.show();
next_button.sensitive = false;
steps_state = "online";
terminal.spawn_async(Vte.PtyFlags.DEFAULT,
settings.get_string("song-folder"),
{"bash"},
null,
GLib.SpawnFlags.DO_NOT_REAP_CHILD,
() => {
},
30,
GLib.Cancellable.get_current(),
(vterm, gid) => {
});
});
// check if there is a folder for the songs to be stored
if (this.settings.get_string("song-folder").length == 0) {
song_folder_button_content.set_label("Set download directory");
song_folder_button.add_css_class("suggested-action");
online_button.set_sensitive(false);
import_button.set_sensitive(false);
} else {
song_folder_button_content.set_label(settings.get_string("song-folder"));
settings.changed.connect(() => {
song_folder_button_content.set_label(settings.get_string("song-folder"));
});
}
download.connect(() => {
navigation_view_steps.push(downloading_page);
string command = "yt-dlp " + "-x -o " + song_title + " --audio-format mp3 " + "'" + song_url + "' \nexit \n"; // \n makes sure that the terminal presses enter after receiving the command
print(command);
terminal.feed_child(command.data);
terminal.set_input_enabled(false);
terminal.child_exited.connect(() => {
Settings _list = new Settings("com.liyaowhen.Song.playlists");
//Check if the song created had no errors
bool file_valid = true;
try {
var file = File.new_for_path(settings.get_string("song-folder") + "/" + song_title + ".mp3");
print("file is \n" + settings.get_string("song-folder") + "/" + song_title + ".mp3");
file.read();
} catch (GLib.Error e) {
print(e.message);
file_valid = false;
}
if (file_valid) {
if (SongController.main_view_content == null) return;
var main_content = SongController.main_view_content;
string[] _string_array = _list.get_strv(main_content.current_playlist);
_string_array += song_title;
_list.set_strv(main_content.current_playlist, _string_array);
foreach(string str in _list.get_strv(main_content.current_playlist)) {
print("this playlist has \n" + str);
}
}
//TODO implement error page
navigation_view_steps.push(complete_page);
size_t length;
print(settings.get_value("songs").dup_string(out length));
});
});
}
}
}
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/liyaowhen/Song">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file preprocess="xml-stripblanks">icons/globe-alt2-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/arrow-into-box-symbolic.svg</file>
</gresource>
</gresources>
+32
View File
@@ -0,0 +1,32 @@
/*
You can type here any CSS rule recognized by GTK.
You can temporarily disable this custom CSS by clicking on the “Pause” button above.
Changes are applied instantly and globally, for the whole application.
*/
window {
background: rgb(238,174,202);
background: linear-gradient(200deg, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
background-repeat: no-repeat;
background-size: cover;
}
.toolbar {
}
navigation-split-view widget.sidebar-pane {
background-color: alpha(rgb(255,255,255), 0.1);
outline: 0;
border-radius: 5px;
margin-left: 5px;
margin-bottom: 5px;
}
label {
filter: none;
}
+164
View File
@@ -0,0 +1,164 @@
namespace Song {
public class MainView : Adw.NavigationPage {
private Window window;
public Settings settings = new Settings("com.liyaowhen.Song.playlists");
public MainViewContent main_content;
public string current_playlist;
public MainView(Window _window) {
this.window = _window;
}
construct {
// make ui
var toolbar_view = new Adw.ToolbarView ();
var scrollable = new Gtk.ScrolledWindow();
main_content = new MainViewContent();
scrollable.set_child(main_content);
scrollable.hexpand = true;
scrollable.hexpand_set = true;
var action_bar = new MainViewActionBar (main_content);
var controls = new SongControls();
toolbar_view.add_top_bar (action_bar);
toolbar_view.set_content(scrollable);
toolbar_view.add_bottom_bar (controls);
this.child = toolbar_view;
this.title = "main_view";
}
}
public class MainViewActionBar : Gtk.Box {
private Gtk.ActionBar action_bar;
private MainViewContent main_view;
public MainViewActionBar (MainViewContent _main_view) {
this.main_view = _main_view;
}
construct {
orientation = Gtk.Orientation.HORIZONTAL;
action_bar = new Gtk.ActionBar();
action_bar.vexpand = true;
action_bar.hexpand = true;
append(action_bar);
}
public void new_music_popover_show() {
var popover = new NewMusicPopover();
popover.present(this);
}
}
public class MainViewContent : Gtk.Box {
private Settings settings = new Settings("com.liyaowhen.Song.playlists");
private Adw.NavigationView navigation_view;
public string current_playlist;
public MainViewContent() {
vexpand = true;
hexpand = true;
print("loading empty ui");
navigation_view = new Adw.NavigationView();
append(navigation_view);
navigation_view.vexpand = true;
navigation_view.hexpand = true;
}
construct {
SongController.main_view_content = this;
}
public void change_page(string playlist_name) {
print("change page called");
bool isKeyValid = false;
foreach (string key in settings.list_keys()){
if (key == playlist_name) {
print("valid playlist");
isKeyValid = true;
}
}
if (!isKeyValid) {
print("invalid playlist_name");
return;
}
current_playlist = playlist_name;
string[] songs = settings.get_strv(playlist_name);
print("\n playlist contains: \n" + songs[1]);
if (songs.length == 1) {
if (songs[0] == "") {empty_playlist_ui(playlist_name); return;}
else {
//
}
} else if (songs.length > 1) {
if (songs[1] != null) {
print("making a page with actual content");
playlist_ui(playlist_name);
}
}
}
private void empty_playlist_ui(string playlist_name) {
var empty_page = new Adw.StatusPage();
var parent_page = new Adw.NavigationPage(empty_page,"no songs added");
empty_page.set_title("Empty Playlist " + playlist_name);
empty_page.set_icon_name("edit-find-symbolic");
empty_page.set_description("Click the button on the top left to add songs in this playlist.");
navigation_view.push(parent_page);
}
private void playlist_ui(string playlist_name) {
var playlist_page = new Adw.StatusPage();
playlist_page.set_title(playlist_name);
playlist_page.set_icon_name("emblem-music-symbolic");
load_music(playlist_page, playlist_name);
var parent_page = new Adw.NavigationPage(playlist_page, playlist_name);
navigation_view.push(parent_page);
}
private void load_music(Adw.StatusPage page, string playlist_name) {
Adw.Clamp clamp = new Adw.Clamp();
Gtk.ListBox content = new Gtk.ListBox();
foreach (string i in settings.get_strv(playlist_name)) {
if (i != "") {
content.append(new MusicListRow(i));
}
}
clamp.child = content;
page.child = clamp;
content.hexpand = true;
}
/*private Gtk.WidgetPaintable playlist_cover(string playlist_name) {
Gtk.WidgetPaintable paintable = new Gtk.WidgetPaintable();
return paintable;
} */
}
}
+64
View File
@@ -0,0 +1,64 @@
namespace Song {
public class MusicListRow : Gtk.Box {
private Settings settings = new Settings("com.liyaowhen.Song.playlists");
public string song_name;
public MusicListRow (string _song_name) {
this.song_name = _song_name;
build_ui();
}
construct {
margin_bottom = 5;
margin_top = 5;
margin_start = 5;
margin_end = 5;
vexpand = true;
hexpand = true;
vexpand_set = true;
hexpand_set = true;
orientation = Gtk.Orientation.HORIZONTAL;
}
private void build_ui() {
var label = new Gtk.Label (song_name);
print(song_name + "making playlists song button");
label.hexpand = true;
append(label);
var play_button = new Gtk.Button.from_icon_name ("media-playback-start-symbolic");
play_button.hexpand = false;
play_button.add_css_class("flat");
play_button.clicked.connect(() => {
if (SongController.song_controls.current_song != song_name) {
SongController.song_controls.change_song(song_name);
play_button.icon_name = "media-playback-pause-symbolic";
Timeout.add(1, () => {
if (SongController.song_controls.current_song != song_name) {
// when another song is called to play, reset the button state
play_button.icon_name = "media-playback-start-symbolic";
return false;
}
return true;
});
} else if (SongController.song_controls.isPlaying) {
SongController.song_controls.pause();
play_button.icon_name = "media-playback-start-symbolic";
} else if (!SongController.song_controls.isPlaying) {
SongController.song_controls.play();
play_button.icon_name = "media-playback-pause-symbolic";
}
});
append(play_button);
spacing = 5;
}
}
}
+31
View File
@@ -0,0 +1,31 @@
namespace Song {
public class Playlist : Gtk.Button {
private Settings settings = new Settings ("com.liyaowhen.Song");
public signal void removal_mode();
public signal void exit_removal_mode();
private string playlist_name;
public Playlist (string _playlist_name) {
this.playlist_name = _playlist_name;
initialize();
}
construct {
hexpand = true;
clicked.connect(() => {
//assumes that main_view is already instanciated
SongController.main_view_content.change_page(playlist_name);
});
}
public void initialize() {
set_label(playlist_name);
}
}
}
+153
View File
@@ -0,0 +1,153 @@
namespace Song {
public class SideBar : Adw.NavigationPage {
private Settings settings = new Settings("com.liyaowhen.Song");
private Window window;
public SideBarContent side_bar_content;
public SideBarControls side_bar_controls;
public SideBar(Window _window) {
this.window = _window;
}
construct {
var toolbar_view = new Adw.ToolbarView();
var scroll_box = new Gtk.ScrolledWindow();
side_bar_content = new SideBarContent ();
side_bar_content.set_vexpand (true);
side_bar_content.set_hexpand (true);
scroll_box.child = side_bar_content;
side_bar_controls = new SideBarControls (side_bar_content);
side_bar_controls.set_hexpand (true);
toolbar_view.set_content(scroll_box);
toolbar_view.add_bottom_bar(side_bar_controls);
title = "Songs:";
this.child = toolbar_view;
}
}
public class SideBarContent : Gtk.Box {
private Settings settings = new Settings("com.liyaowhen.Song.playlists");
public List<Playlist> playlists;
private MainView main_view;
public SideBarContent() {
}
construct {
orientation = Gtk.Orientation.VERTICAL;
valign = Gtk.Align.START;
spacing = 5;
margin_bottom = 5;
margin_top = 5;
margin_start = 5;
margin_end = 5;
vexpand = true;
hexpand = true;
vexpand_set = true;
hexpand_set = true;
Playlist initial_playlist;
var playlist_names = settings.get_strv("playlists");
foreach (string _name in playlist_names) {
var playlist = new Playlist(_name);
if (_name == playlist_names[0]) {
initial_playlist = playlist;
}
playlists.append(playlist);
append(playlist);
}
print("sidbar requesting main_view_content switch to playlist of name:\n" + playlist_names[1]);
Timeout.add(1, () => {
if (SongController.main_view_content != null) {
SongController.main_view_content.change_page(playlist_names[0]);
return false;
}
return true;
}, 1);
SongController.sidebar_content = this;
//reconfigure_playlist_button_group(); DOES NOT WORK FOR SOME REASON
}
/*private void reconfigure_playlist_button_group() {
foreach (Playlist i in playlists) {
foreach (Playlist j in playlists) {
if (i != j) {
i.set_group(j);
}
}
}
}*/
}
public class SideBarControls : Gtk.Box {
private SideBarContent sidebar_content;
private Gtk.ActionBar action_bar;
public SideBarControls(SideBarContent _sidebar_content) {
this.sidebar_content = _sidebar_content;
}
construct {
action_bar = new Gtk.ActionBar();
action_bar.vexpand = true;
action_bar.hexpand = true;
append(action_bar);
this.orientation = Gtk.Orientation.HORIZONTAL;
this.add_css_class ("toolbar");
// make ui
var select_button = new Gtk.ToggleButton();
select_button.set_icon_name ("checkbox-symbolic");
var delete_button = new Gtk.Button();
delete_button.set_icon_name ("user-trash-symbolic");
var export_button = new Gtk.Button();
export_button.set_icon_name("document-save-symbolic");
var options_button = new Gtk.Button(); // TODO: change to Gtk.MenuButton
options_button.set_icon_name("open-menu-symbolic");
options_button.sensitive = true;
action_bar.pack_start(select_button);
//action_bar.set_center_widget(empty_spacer());
action_bar.pack_end(options_button);
// signals
}
private Gtk.Separator empty_spacer() {
var empty_spacer = new Gtk.Separator(Gtk.Orientation.VERTICAL);
empty_spacer.hexpand = true;
empty_spacer.add_css_class("spacer");
return empty_spacer;
}
}
}
+162
View File
@@ -0,0 +1,162 @@
using Gst;
namespace Song {
public class SongControls : Gtk.Box {
private Settings songs_list = new Settings("com.liyaowhen.Song.playlists");
private Settings settings = new Settings("com.liyaowhen.Song");
public Gtk.Scale progress_bar;
public Gtk.Adjustment progress_bar_adjustment;
private bool isDragging;
public bool isPlaying;
public string current_song;
public Gst.Element playbin;
public SongControls() {
playbin = Gst.ElementFactory.make("playbin", "player");
}
construct {
// Gstreamer stuff
isPlaying = false;
// UI
orientation = Gtk.Orientation.VERTICAL;
vexpand = false;
hexpand = true;
hexpand_set = true;
progress_bar_adjustment = new Gtk.Adjustment(0, 0, 1, 1, 1, 0);
progress_bar = new Gtk.Scale(Gtk.Orientation.HORIZONTAL, progress_bar_adjustment);
progress_bar.set_draw_value(false);
Timeout.add(1, () => {
if (playbin != null && isDragging != true) {
var position = (int64) 0;
var duration = (int64) 0;
// Query the current position and duration
if (playbin.query_position(Format.TIME, out position) &&
playbin.query_duration(Format.TIME, out duration)) {
progress_bar.set_range(0, (double) duration / Gst.SECOND);
progress_bar.set_value((double) position / Gst.SECOND);
}
}
//print("running");
return true;
});
uint timeout_id = 0;
progress_bar.change_value.connect((range) => {
if (timeout_id != 0) {
try {
Source.remove(timeout_id);
} catch (GLib.Error e) {
// the function above often complains
}
}
isDragging = true;
if (playbin != null) {
var seek_time = (int64)(progress_bar.get_value() * Gst.SECOND);
playbin.seek_simple(Format.TIME, SeekFlags.FLUSH | SeekFlags.KEY_UNIT, seek_time);
}
timeout_id = Timeout.add(50, () => {
isDragging = false;
return false;
});
}); // emitted when user (not program) changes the scale
progress_bar.value_changed.connect((range) => {
});
append(progress_bar);
// add controls to control the music
music_controls();
SongController.song_controls = this;
}
private void music_controls() {
var clamp = new Adw.Clamp();
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
box.hexpand = true;
box.halign = Gtk.Align.CENTER;
box.margin_bottom = 10;
var play_button = new Gtk.Button.from_icon_name("media-playback-start-symbolic");
play_button.halign = Gtk.Align.CENTER;
Timeout.add(1, () => {
if (isPlaying == true) {
play_button.set_icon_name("media-playback-pause-symbolic");
} else {
play_button.set_icon_name("media-playback-start-symbolic");
}
return true;
});
play_button.clicked.connect(() => {
if (isPlaying) pause();
else play();
});
box.append(play_button);
play_button.clicked.connect(() => {
play_music();
});
clamp.child = box;
append(clamp);
}
private void play_music() {
if (current_song != null) {
}
}
public void play() {
playbin.set_state(Gst.State.PLAYING);
isPlaying = true;
}
public void pause() {
playbin.set_state(Gst.State.PAUSED);
isPlaying = false;
}
public void change_song(string song) {
print("song is now " + song + "\n");
current_song = song;
string file_path = "file://" + settings.get_string("song-folder") + "/" + current_song + ".mp3";
print("song full path is " + file_path);
playbin.set_state(Gst.State.NULL);
playbin.set_property("uri", file_path);
playbin.set_state(Gst.State.PLAYING);
isPlaying = true;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="Adw" version="1.0"/>
<template class="SongWindow" parent="AdwApplicationWindow">
<property name="default-width">600</property>
<property name="default-height">300</property>
<property name="content">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar" id="header_bar">
<child type="end">
<object class="GtkMenuButton">
<property name="primary">True</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="tooltip-text" translatable="yes">Menu</property>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<property name="content">
<object class="GtkLabel" id="label">
<property name="label">Hello, World!</property>
<style>
<class name="title-1"/>
</style>
</object>
</property>
</object>
</property>
</template>
<menu id="primary_menu">
<section>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
<attribute name="action">win.show-help-overlay</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About Song</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>
+85
View File
@@ -0,0 +1,85 @@
namespace Song {
public class Window : Adw.ApplicationWindow {
private Settings settings = new Settings ("com.liyaowhen.Song");
public SideBar sidebar;
public MainView main_view;
public Window (Gtk.Application app) {
Object (application: app);
}
construct {
var gesture = new Gtk.GestureDrag();
var provider = new Gtk.CssProvider();
provider.load_from_path("style.css");
this.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
// GSettings
this.settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT);
this.settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT);
this.settings.bind ("window-maximized", this, "maximized", SettingsBindFlags.DEFAULT);
// UI
var title_bar = new Adw.HeaderBar ();
//title_bar.set_decoration_layout("icon:close");
title_bar.show_title = false;
var add_button = new Gtk.Button();
var add_button_content = new Adw.ButtonContent ();
add_button_content.set_icon_name("tab-new-symbolic");
add_button_content.set_label("new song");
add_button_content.set_parent(add_button);
add_button.clicked.connect(() => {
new_music_popover_show();
});
var collapse_button = new Gtk.Button.from_icon_name ("folder-open-symbolic");
title_bar.pack_start (collapse_button);
title_bar.pack_start(add_button);
main_view = new MainView(this);
sidebar = new SideBar(this);
var split_view = new Adw.NavigationSplitView ();
split_view.set_sidebar (sidebar);
split_view.set_content(main_view);
split_view.show_content = true;
var _content = new Adw.ToolbarView ();
_content.add_top_bar (title_bar);
_content.set_content (split_view);
_content.add_controller(gesture);
SongController.gesture = gesture;
collapse_button.clicked.connect(() => {
if(split_view.get_collapsed ()){
split_view.set_collapsed (false);
collapse_button.set_icon_name("folder-open-symbolic");
} else {
split_view.set_collapsed(true);
collapse_button.set_icon_name("folder-symbolic");
}
});
set_content(_content);
}
public void new_music_popover_show() {
var popover = new NewMusicPopover();
popover.present(this);
}
}
}