package com.xenoage.zong.gui; import com.xenoage.utils.jse.files.RecentFiles; import com.xenoage.utils.jse.javafx.Dialog; import com.xenoage.utils.jse.javafx.ResourceUpdater; import com.xenoage.utils.jse.lang.LangManager; import com.xenoage.utils.jse.lang.LangResourceBundle; import com.xenoage.utils.jse.lang.LanguageInfo; import com.xenoage.utils.jse.lang.LanguageListener; import com.xenoage.utils.lang.Lang; import com.xenoage.zong.Voc; import com.xenoage.zong.Zong; import com.xenoage.zong.commands.desktop.app.*; import com.xenoage.zong.commands.desktop.dialog.AudioSettingsDialogShow; import com.xenoage.zong.commands.desktop.dialog.FeedbackDialogShow; import com.xenoage.zong.commands.desktop.dialog.OpenDocumentDialog; import com.xenoage.zong.commands.desktop.dialog.SaveDocumentDialog; import com.xenoage.zong.commands.player.convert.DirToMidiConvert; import com.xenoage.zong.commands.player.convert.FileToMidiConvert; import com.xenoage.zong.commands.player.dialog.AboutDialogShow; import com.xenoage.zong.commands.player.dialog.InfoDialogShow; import com.xenoage.zong.commands.player.playback.PlaybackPause; import com.xenoage.zong.commands.player.playback.PlaybackStart; import com.xenoage.zong.commands.player.playback.PlaybackStop; import com.xenoage.zong.core.Score; import com.xenoage.zong.core.position.Time; import com.xenoage.zong.desktop.io.midi.out.MidiScorePlayer; import com.xenoage.zong.io.midi.out.PlaybackListener; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import static com.xenoage.utils.NullUtils.notNull; import static com.xenoage.utils.collections.CollectionUtils.alist; import static com.xenoage.utils.collections.CollectionUtils.map; import static com.xenoage.utils.error.Err.handle; import static com.xenoage.utils.jse.JsePlatformUtils.io; import static com.xenoage.utils.log.Report.fatal; import static com.xenoage.zong.desktop.App.app; import static com.xenoage.zong.desktop.utils.ImageUtils.imageOrNull; import static com.xenoage.zong.player.Player.pApp; import static java.util.Comparator.naturalOrder; public class PlayerFrame extends Dialog implements PlaybackListener, LanguageListener { //menu @FXML private Menu mnuFile; @FXML private MenuItem mnuFileOpen; @FXML private MenuItem mnuFileSaveAs; @FXML private MenuItem mnuFileInfo; @FXML private SeparatorMenuItem mnuFileSepRecentFiles; @FXML private Menu mnuFileDemoScores; @FXML private SeparatorMenuItem mnuFileSepDemoScores; @FXML private MenuItem mnuFileExit; @FXML private Menu mnuConvert; @FXML private MenuItem mnuConvertFileToMidi; @FXML private MenuItem mnuConvertDirToMidi; @FXML private Menu mnuSettings; @FXML private MenuItem mnuSettingsAudio; @FXML private Menu mnuSettingsLanguage; @FXML private Menu mnuHelp; @FXML private MenuItem mnuHelpReadme; @FXML private MenuItem mnuHelpWebsite; @FXML private MenuItem mnuHelpBlog; @FXML private MenuItem mnuHelpAbout; @FXML private Menu mnuBeta; @FXML private MenuItem mnuBetaReport; //recent files menu items private List<MenuItem> mnusRecentFiles = alist(); //playback area @FXML private Label lblTitle; @FXML private Label lblTimePos; @FXML private ProgressBar progress; @FXML private Label lblTimeDuration; //controls @FXML private Button btnOpen; @FXML private Button btnStart; @FXML private Button btnPause; @FXML private Button btnStop; @FXML private ImageView imgVolume; @FXML private Slider sliderVolume; @FXML private Button btnSave; @FXML private Button btnInfo; private Map<String, Image> volumeIcons = map(); public void initialize() { //load icons volumeIcons.put("high", readImage("audio-volume-high.png")); volumeIcons.put("medium", readImage("audio-volume-medium.png")); volumeIcons.put("low", readImage("audio-volume-low.png")); volumeIcons.put("muted", readImage("audio-volume-muted.png")); //reset values lblTimePos.setText(""); lblTimeDuration.setText(""); progress.setProgress(0); sliderVolume.setValue(70); imgVolume.setImage(volumeIcons.get("medium")); //recent files list RecentFiles.addListener(this::updateRecentFiles); updateRecentFiles(); //demo files list updateDemoFiles(); //list of languages createLanguageItems(); //handle progress bar clicks pApp().getPlayer().addPlaybackListener(this); //handle volume slider events sliderVolume.valueProperty().addListener((property, oldValue, newValue) -> { float value = newValue.floatValue() * 0.01f; pApp().getPlayer().setVolume(value); String s = "muted"; if (value > 0.8f) s = "high"; else if (value > 0.4f) s = "medium"; else if (value > 0f) s = "low"; imgVolume.setImage(volumeIcons.get(s)); }); //register for language changes LangManager.registerComponent(this); } private Image readImage(String filename) { return new Image(getClass().getResourceAsStream("img/" + filename)); } private void updateRecentFiles() { //clear old menu items for (MenuItem mnu : mnusRecentFiles) { mnuFile.getItems().remove(mnu); } //add new menu items List<File> recentFiles = RecentFiles.getRecentFiles(); int recentFilesMenuOffset = mnuFile.getItems().indexOf(mnuFileSepRecentFiles) + 1; for (int i = 0; i < recentFiles.size() && i < 10; i++) { final File file = recentFiles.get(i); MenuItem mnu = new MenuItem((i + 1) + ": " + file.getName()); mnu.setOnAction(e -> app().execute(new DocumentOpen(file.getAbsolutePath()))); mnusRecentFiles.add(mnu); mnuFile.getItems().add(recentFilesMenuOffset + i, mnu); } //show seperator only, if there is at least one file mnuFileSepRecentFiles.setVisible(recentFiles.size() > 0); } private void updateDemoFiles() { //clear old menu items mnuFileDemoScores.getItems().clear(); //add menu items final String dir = "data/demo/scores/"; List<String> files = io().listFiles(dir); files.sort(naturalOrder()); for (String file : files) { MenuItem mnu = new MenuItem(file); mnu.setOnAction(e -> app().execute(new DocumentOpen(dir + file))); mnuFileDemoScores.getItems().add(mnu); } //show menu and separator only, if there is at least one file boolean visible = files.size() > 0; mnuFileDemoScores.setVisible(visible); mnuFileSepDemoScores.setVisible(visible); } private void createLanguageItems() { //clear old menu items mnuSettingsLanguage.getItems().clear(); //add language menu items List<LanguageInfo> langs = null; try { langs = LanguageInfo.getAvailableLanguages(LangManager.defaultLangPath); } catch (Exception ex) { handle(fatal(ex)); } ToggleGroup toggleGroup = new ToggleGroup(); for (final LanguageInfo lang : langs) { String name = lang.getLocalName(); String intName = lang.getInternationalName(); ImageView icon = null; if (lang.getFlag16Path() != null) icon = new ImageView(imageOrNull(lang.getFlag16Path())); String text = name + (name.equals(intName) ? "" : " (" + intName + ")"); RadioMenuItem mnu = new RadioMenuItem(text, icon); mnu.setToggleGroup(toggleGroup); mnu.setOnAction(e -> app().execute(new LanguageChange(lang.getID()))); if (Lang.getCurrentLanguage().getID().equals(lang.getID())) mnu.setSelected(true); mnu.setUserData(lang.getID()); mnuSettingsLanguage.getItems().add(mnu); } } public void setScore(Score score) { //set title String title = notNull(score.getInfo().getComposer()); if (title.length() > 0) title += " - "; title += notNull(score.getInfo().getTitle()); lblTitle.setText(title); } @Override public void playbackAtTime(Time time, long ms) { } @Override public void playbackAtMs(long ms) { MidiScorePlayer player = pApp().getPlayer(); long lengthMs = player.getMicrosecondLength() / 1000; Platform.runLater(() -> { if (lengthMs > 0) { progress.setProgress(1.0 * ms / lengthMs); lblTimePos.setText(formatTime(ms)); lblTimeDuration.setText(formatTime(lengthMs)); } else { progress.setProgress(0); lblTimePos.setText(""); lblTimeDuration.setText(""); } }); } @Override public void playbackStarted() { } @Override public void playbackPaused() { } @Override public void playbackStopped() { playbackAtMs(0); } @Override public void playbackAtEnd() { playbackAtMs(0); } //TIDY: same code somewhere in Viewer private String formatTime(long timeMs) { long timeS = timeMs / 1000; String mins = String.valueOf(timeS / 60); String secs = String.valueOf(timeS % 60); if (mins.length() < 2) mins = "0" + mins; if (secs.length() < 2) secs = "0" + secs; return mins + ":" + secs; } @FXML void onOpen(ActionEvent event) { app().execute(new OpenDocumentDialog(stage)); } @FXML void onSave(ActionEvent event) { app().execute(new SaveDocumentDialog(stage)); } @FXML void onInfo(ActionEvent event) { if (pApp().getActiveDocument() == null) { pApp().showMessageDialog(Lang.get(Voc.NoFileLoaded)); } else { app().execute(new InfoDialogShow(pApp().getActiveDocument(), stage)); } } @FXML void onExit(ActionEvent event) { app().execute(new Exit()); } @FXML void onConvertFileToMidi(ActionEvent event) { app().execute(new FileToMidiConvert(stage)); } @FXML void onConvertDirToMidi(ActionEvent event) { app().execute(new DirToMidiConvert(stage)); } @FXML void onSettingsAudio(ActionEvent event) { app().execute(new AudioSettingsDialogShow(stage)); } @FXML void onReadme(ActionEvent event) { app().execute(new ExternalFileOpen("readme.txt")); } @FXML void onWebsite(ActionEvent event) { app().execute(new WebsiteOpen(Zong.website)); } @FXML void onBlog(ActionEvent event) { app().execute(new WebsiteOpen(Zong.blog)); } @FXML void onAbout(ActionEvent event) { app().execute(new AboutDialogShow(stage)); } @FXML void onErrorReport(ActionEvent event) { app().execute(new FeedbackDialogShow(stage)); } @FXML void onStart(ActionEvent event) { app().execute(new PlaybackStart()); } @FXML void onPause(ActionEvent event) { app().execute(new PlaybackPause()); } @FXML void onStop(ActionEvent event) { app().execute(new PlaybackStop()); } @FXML void onProgressBarClick(MouseEvent event) { MidiScorePlayer player = pApp().getPlayer(); if (player.getSequence() != null) { double pos = event.getX() / progress.getWidth(); long mis = (long) (pos * player.getMicrosecondLength()); player.setMicrosecondPosition(mis); playbackAtMs(mis / 1000); } } @Override public void languageChanged() { //reload the resources of the frame try { ResourceUpdater.updateScene(this, PlayerFrame.class.getResourceAsStream("PlayerFrame.fxml"), new LangResourceBundle(Voc.values())); } catch (IOException ex) { ex.printStackTrace(); } //select the corresponding menu item (ID stored in user data) for (MenuItem item : mnuSettingsLanguage.getItems()) { if (Lang.getCurrentLanguage().getID().equals(item.getUserData())) ((RadioMenuItem) item).setSelected(true); } } }