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)