diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java new file mode 100644 index 00000000..c468631c --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java @@ -0,0 +1,343 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.gui.playlist.alias; + +import com.google.common.collect.Ordering; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.gui.playlist.Editor; +import io.github.dsheirer.icon.Icon; +import io.github.dsheirer.playlist.PlaylistManager; +import javafx.collections.transformation.SortedList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.util.Callback; +import org.controlsfx.control.ToggleSwitch; + +import java.util.List; + +/** + * Editor for multiple selected aliases providing limited options for changing attributes of multiple aliases + */ +public class AliasBulkEditor extends Editor> +{ + private PlaylistManager mPlaylistManager; + private Label mEditingLabel; + private ColorPicker mColorPicker; + private Button mApplyColorButton; + private ComboBox mIconNodeComboBox; + private Button mApplyIconButton; + private ToggleSwitch mMonitorAudioToggleSwitch; + private ComboBox mMonitorPriorityComboBox; + private Button mApplyMonitorButton; + + /** + * Constructs an instance + * @param playlistManager for accessing icon manager + */ + public AliasBulkEditor(PlaylistManager playlistManager) + { + mPlaylistManager = playlistManager; + + GridPane gridPane = new GridPane(); + gridPane.setPadding(new Insets(10,10,10,10)); + gridPane.setHgap(10); + gridPane.setVgap(10); + + int row = 0; + + GridPane.setConstraints(getEditingLabel(), 1, row, 2, 1); + gridPane.getChildren().add(getEditingLabel()); + + Label colorLabel = new Label("Color"); + GridPane.setHalignment(colorLabel, HPos.RIGHT); + GridPane.setConstraints(colorLabel, 0, ++row); + gridPane.getChildren().add(colorLabel); + + GridPane.setConstraints(getColorPicker(), 1, row, 3, 1); + gridPane.getChildren().add(getColorPicker()); + + GridPane.setConstraints(getApplyColorButton(), 4, row); + gridPane.getChildren().add(getApplyColorButton()); + + Label iconLabel = new Label("Icon"); + GridPane.setHalignment(iconLabel, HPos.RIGHT); + GridPane.setConstraints(iconLabel, 0, ++row); + gridPane.getChildren().add(iconLabel); + + GridPane.setConstraints(getIconNodeComboBox(), 1, row, 3, 1); + gridPane.getChildren().add(getIconNodeComboBox()); + + GridPane.setConstraints(getApplyIconButton(), 4, row); + gridPane.getChildren().add(getApplyIconButton()); + + Label listenLabel = new Label("Listen"); + GridPane.setHalignment(listenLabel, HPos.RIGHT); + GridPane.setConstraints(listenLabel, 0, ++row); + gridPane.getChildren().add(listenLabel); + + GridPane.setConstraints(getMonitorAudioToggleSwitch(), 1, row); + gridPane.getChildren().add(getMonitorAudioToggleSwitch()); + + Label priorityLabel = new Label("Priority"); + GridPane.setHalignment(priorityLabel, HPos.RIGHT); + GridPane.setConstraints(priorityLabel, 2, row); + gridPane.getChildren().add(priorityLabel); + + GridPane.setConstraints(getMonitorPriorityComboBox(), 3, row); + gridPane.getChildren().add(getMonitorPriorityComboBox()); + + GridPane.setConstraints(getApplyMonitorButton(), 4, row); + gridPane.getChildren().add(getApplyMonitorButton()); + + getChildren().add(gridPane); + } + + @Override + public List getItem() + { + return super.getItem(); + } + + @Override + public void setItem(List item) + { + super.setItem(item); + getEditingLabel().setText("Editing " + item.size() + " Aliases"); + } + + @Override + public void save() + { + //no-op + } + + @Override + public void dispose() + { + //no-op + } + + private Label getEditingLabel() + { + if(mEditingLabel== null) + { + mEditingLabel = new Label("Editing 0 Aliases"); + } + + return mEditingLabel; + } + + private ColorPicker getColorPicker() + { + if(mColorPicker == null) + { + mColorPicker = new ColorPicker(Color.BLACK); + mColorPicker.setEditable(true); + mColorPicker.setStyle("-fx-color-rect-width: 60px; -fx-color-label-visible: false;"); + } + + return mColorPicker; + } + + private Button getApplyColorButton() + { + if(mApplyColorButton == null) + { + mApplyColorButton = new Button("Apply"); + mApplyColorButton.setOnAction(event -> { + int colorValue = ColorUtil.toInteger(getColorPicker().getValue()); + + for(Alias alias: getItem()) + { + alias.setColor(colorValue); + } + }); + } + + return mApplyColorButton; + } + + private ComboBox getIconNodeComboBox() + { + if(mIconNodeComboBox == null) + { + mIconNodeComboBox = new ComboBox<>(); + mIconNodeComboBox.setMaxWidth(Double.MAX_VALUE); + mIconNodeComboBox.setItems(new SortedList(mPlaylistManager.getIconModel().iconsProperty(), Ordering.natural())); + mIconNodeComboBox.setCellFactory(new IconCellFactory()); + mIconNodeComboBox.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> + { + getApplyIconButton().setDisable(newValue == null); + }); + } + + return mIconNodeComboBox; + } + + private Button getApplyIconButton() + { + if(mApplyIconButton == null) + { + mApplyIconButton = new Button("Apply"); + mApplyIconButton.setDisable(true); + mApplyIconButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + Icon icon = getIconNodeComboBox().getSelectionModel().getSelectedItem(); + + if(icon != null) + { + for(Alias alias: getItem()) + { + alias.setIconName(icon.getName()); + } + } + } + }); + } + + return mApplyIconButton; + } + + private ToggleSwitch getMonitorAudioToggleSwitch() + { + if(mMonitorAudioToggleSwitch == null) + { + mMonitorAudioToggleSwitch = new ToggleSwitch(); + mMonitorAudioToggleSwitch.setSelected(true); + mMonitorAudioToggleSwitch.selectedProperty() + .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); + } + + return mMonitorAudioToggleSwitch; + } + + private ComboBox getMonitorPriorityComboBox() + { + if(mMonitorPriorityComboBox == null) + { + mMonitorPriorityComboBox = new ComboBox<>(); + mMonitorPriorityComboBox.getItems().add(null); + for(int x = io.github.dsheirer.alias.id.priority.Priority.MIN_PRIORITY; + x < io.github.dsheirer.alias.id.priority.Priority.MAX_PRIORITY; x++) + { + mMonitorPriorityComboBox.getItems().add(x); + } + + mMonitorPriorityComboBox.disableProperty().bind(getMonitorAudioToggleSwitch().selectedProperty().not()); + mMonitorPriorityComboBox.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); + } + + return mMonitorPriorityComboBox; + } + + private Button getApplyMonitorButton() + { + if(mApplyMonitorButton == null) + { + mApplyMonitorButton = new Button("Apply"); + mApplyMonitorButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + boolean canMonitor = getMonitorAudioToggleSwitch().isSelected(); + Integer priority = getMonitorPriorityComboBox().getSelectionModel().getSelectedItem(); + if(canMonitor) + { + if(priority == null) + { + priority = io.github.dsheirer.alias.id.priority.Priority.DEFAULT_PRIORITY; + } + } + else + { + priority = io.github.dsheirer.alias.id.priority.Priority.DO_NOT_MONITOR; + } + + for(Alias alias: getItem()) + { + alias.setCallPriority(priority); + } + } + }); + } + + return mApplyMonitorButton; + } + + /** + * Cell factory for combo box for dislaying icon name and graphic + */ + public class IconCellFactory implements Callback, ListCell> + { + @Override + public ListCell call(ListView param) + { + Label iconLabel = new Label(); + Label textLabel = new Label(); + GridPane gridPane = new GridPane(); + gridPane.setHgap(5); + GridPane.setHalignment(iconLabel, HPos.RIGHT); + gridPane.getColumnConstraints().add(new ColumnConstraints(50)); + gridPane.add(iconLabel, 0, 0); + gridPane.add(textLabel,1,0); + + ListCell cell = new ListCell<>() + { + @Override + protected void updateItem(Icon item, boolean empty) + { + super.updateItem(item, empty); + + if(empty) + { + setText(null); + setGraphic(null); + } + else + { + textLabel.setText(item.getName()); + iconLabel.setGraphic(new ImageView(item.getFxImage())); + setGraphic(gridPane); + } + } + }; + + return cell; + } + } +} diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java index 3a706158..a2c47fe4 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java @@ -27,12 +27,14 @@ import io.github.dsheirer.alias.AliasFactory; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.gui.control.MaxLengthUnaryOperator; +import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.icon.Icon; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.preference.UserPreferences; import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; @@ -85,6 +87,8 @@ public class AliasConfigurationEditor extends SplitPane private PlaylistManager mPlaylistManager; private UserPreferences mUserPreferences; private AliasItemEditor mAliasItemEditor; + private AliasBulkEditor mAliasBulkEditor; + private Editor mCurrentEditor; private TableView mAliasTableView; private Label mPlaceholderLabel; private Button mNewAliasButton; @@ -114,6 +118,7 @@ public class AliasConfigurationEditor extends SplitPane topBox.getChildren().addAll(leftBox, getButtonBox()); setOrientation(Orientation.VERTICAL); + mCurrentEditor = getAliasItemEditor(); getItems().addAll(topBox, getAliasItemEditor()); } @@ -141,7 +146,20 @@ public class AliasConfigurationEditor extends SplitPane } } - private void setAlias(Alias alias) + /** + * Sets the editor as the bottom alias editor, either single alias or bulk alias editor. + */ + private void setEditor(Editor editor) + { + if(editor != mCurrentEditor) + { + getItems().remove(mCurrentEditor); + mCurrentEditor = editor; + getItems().add(mCurrentEditor); + } + } + + private void setAliases(List aliases) { //Prompt the user to save if the contents of the current channel editor have been modified if(getAliasItemEditor().modifiedProperty().get()) @@ -168,18 +186,27 @@ public class AliasConfigurationEditor extends SplitPane } } - getCloneAliasButton().setDisable(alias == null || getAliasTableView().getSelectionModel().getSelectedItems().size() > 1); - getDeleteAliasButton().setDisable(alias == null); - getMoveToAliasButton().setDisable(alias == null); - - if(getAliasTableView().getSelectionModel().getSelectedItems().size() <= 1) + if(aliases.size() <= 1) { - getAliasItemEditor().setItem(alias); + setEditor(getAliasItemEditor()); + if(aliases.size() == 1) + { + getAliasItemEditor().setItem(aliases.get(0)); + } + else + { + getAliasItemEditor().setItem(null); + } } else { - getAliasItemEditor().setItem(null); + setEditor(getAliasBulkEditor()); + getAliasBulkEditor().setItem(aliases); } + + getCloneAliasButton().setDisable(aliases.isEmpty() || aliases.size() > 1); + getDeleteAliasButton().setDisable(aliases.isEmpty()); + getMoveToAliasButton().setDisable(aliases.isEmpty()); } private AliasItemEditor getAliasItemEditor() @@ -192,6 +219,16 @@ public class AliasConfigurationEditor extends SplitPane return mAliasItemEditor; } + private AliasBulkEditor getAliasBulkEditor() + { + if(mAliasBulkEditor == null) + { + mAliasBulkEditor = new AliasBulkEditor(mPlaylistManager); + } + + return mAliasBulkEditor; + } + private HBox getSearchAndListSelectionBox() { if(mSearchAndListSelectionBox == null) @@ -384,10 +421,9 @@ public class AliasConfigurationEditor extends SplitPane mAliasTableView.setPlaceholder(getPlaceholderLabel()); mAliasTableView.setItems(getAliasSortedList()); mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - mAliasTableView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - setAlias(newValue); - }); + mAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener)c -> { + setAliases(mAliasTableView.getSelectionModel().getSelectedItems()); + }); } return mAliasTableView; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java index b10d5540..a2441cce 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java @@ -335,12 +335,12 @@ public class AliasItemEditor extends Editor alias.setRecordable(getRecordAudioToggleSwitch().isSelected()); alias.setColor(ColorUtil.toInteger(getColorPicker().getValue())); - boolean canMonitor = getMonitorAudioToggleSwitch().isSelected(); - Integer priority = getMonitorPriorityComboBox().getSelectionModel().getSelectedItem(); Icon icon = getIconNodeComboBox().getSelectionModel().getSelectedItem(); alias.setIconName(icon != null ? icon.getName() : null); + boolean canMonitor = getMonitorAudioToggleSwitch().isSelected(); + Integer priority = getMonitorPriorityComboBox().getSelectionModel().getSelectedItem(); if(canMonitor) { if(priority == null)