Skip to content

Commit 30d4cb9

Browse files
committed
the sections in the UI should not jump back to the top when the song is scrolled; execute the display code inside a SwingUtilities.invokerLater() to reduce erroneous artifacts in the text on the projector
1 parent 106f25c commit 30d4cb9

File tree

6 files changed

+135
-75
lines changed

6 files changed

+135
-75
lines changed

src/main/java/org/zephyrsoft/sdb2/MainController.java

+27-8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Iterator;
3535
import java.util.List;
3636
import java.util.Locale;
37+
import java.util.Objects;
3738
import java.util.concurrent.Executor;
3839
import java.util.concurrent.ExecutorService;
3940
import java.util.concurrent.Executors;
@@ -54,6 +55,7 @@
5455
import org.zephyrsoft.sdb2.gui.MainWindow;
5556
import org.zephyrsoft.sdb2.model.AddressablePart;
5657
import org.zephyrsoft.sdb2.model.FilterTypeEnum;
58+
import org.zephyrsoft.sdb2.model.PresentCommandResult;
5759
import org.zephyrsoft.sdb2.model.ScreenContentsEnum;
5860
import org.zephyrsoft.sdb2.model.SelectableDisplay;
5961
import org.zephyrsoft.sdb2.model.Song;
@@ -118,7 +120,9 @@ public class MainController implements Scroller {
118120
private List<SelectableDisplay> screens;
119121
private PresenterBundle presentationControl;
120122
private Song currentlyPresentedSong = null;
121-
123+
private Integer currentlyPresentedSongPartIndex = null;
124+
private Integer currentlyPresentedSongLineIndex = null;
125+
122126
private ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
123127
private Future<?> countDownFuture;
124128
private Iterator<File> slideShowImages;
@@ -220,15 +224,23 @@ public void contentChange(Runnable command) {
220224
contentChanger.execute(command);
221225
}
222226

223-
public boolean present(Presentable presentable, PresentationPosition presentationPosition) {
227+
public PresentCommandResult present(Presentable presentable, PresentationPosition presentationPosition) {
224228
if (presentable == null) {
225229
throw new IllegalArgumentException("the given presentable must be non-null");
226230
}
227231
if (presentable.getSong() != null
232+
&& presentable.getSong().equals(currentlyPresentedSong)
233+
&& presentationPosition instanceof SongPresentationPosition spp
234+
&& Objects.equals(spp.getPartIndex(), currentlyPresentedSongPartIndex)
235+
&& Objects.equals(spp.getLineIndex(), currentlyPresentedSongLineIndex)) {
236+
return PresentCommandResult.NOTHING_TO_DO;
237+
} else if (presentable.getSong() != null
228238
&& presentable.getSong().equals(currentlyPresentedSong)
229239
&& presentationPosition instanceof SongPresentationPosition spp) {
230240
presentationControl.moveTo(spp);
231-
return true;
241+
currentlyPresentedSongPartIndex = spp.getPartIndex();
242+
currentlyPresentedSongLineIndex = spp.getLineIndex();
243+
return PresentCommandResult.ONLY_SCOLLED;
232244
}
233245

234246
SelectableDisplay screen1 = ScreenHelper.getScreen(screens, settings.get(SettingKey.SCREEN_1_DISPLAY, Integer.class));
@@ -253,14 +265,21 @@ public boolean present(Presentable presentable, PresentationPosition presentatio
253265
&& pw.screenSizeMatches()))) {
254266
LOG.trace("re-using the existing presenters");
255267
currentlyPresentedSong = presentable.getSong();
268+
if (presentationPosition instanceof SongPresentationPosition spp) {
269+
currentlyPresentedSongPartIndex = spp.getPartIndex();
270+
currentlyPresentedSongLineIndex = spp.getLineIndex();
271+
} else {
272+
currentlyPresentedSongPartIndex = null;
273+
currentlyPresentedSongLineIndex = null;
274+
}
256275
managePresentedSongStatistics();
257276
presentationControl.setContent(presentable, presentationPosition);
258277

259278
// the presentation windows were moved to front and got the focus because of that,
260279
// so we need to get the focus back to the main window:
261280
mainWindow.toFront();
262281

263-
return true;
282+
return PresentCommandResult.SUCCESS;
264283
} else {
265284
LOG.trace("using newly created presenters");
266285
return presentInNewPresenters(presentable, presentationPosition, screen1, screen2);
@@ -275,7 +294,7 @@ private void managePresentedSongStatistics() {
275294
}
276295
}
277296

278-
private boolean presentInNewPresenters(Presentable presentable, PresentationPosition presentationPosition, SelectableDisplay screen1,
297+
private PresentCommandResult presentInNewPresenters(Presentable presentable, PresentationPosition presentationPosition, SelectableDisplay screen1,
279298
SelectableDisplay screen2) {
280299
PresenterBundle oldPresentationControl = presentationControl;
281300
presentationControl = new PresenterBundle();
@@ -295,10 +314,10 @@ private boolean presentInNewPresenters(Presentable presentable, PresentationPosi
295314
.openDialog(
296315
null,
297316
PRESENTATION_DISPLAY_WARNING);
298-
return false;
317+
return PresentCommandResult.FAILURE;
299318
} else {
300319
if (remoteController != null) {
301-
presentationControl.addPresenter(remoteController.getRemotePresenter(presentable));
320+
presentationControl.addPresenter(remoteController.getRemotePresenter(presentable, presentationPosition));
302321
}
303322

304323
currentlyPresentedSong = presentable.getSong();
@@ -324,7 +343,7 @@ private boolean presentInNewPresenters(Presentable presentable, PresentationPosi
324343
}
325344
});
326345

327-
return true;
346+
return PresentCommandResult.SUCCESS;
328347
}
329348
}
330349

src/main/java/org/zephyrsoft/sdb2/gui/MainWindow.java

+45-29
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.zephyrsoft.sdb2.model.AddressablePart;
6767
import org.zephyrsoft.sdb2.model.ExportFormat;
6868
import org.zephyrsoft.sdb2.model.FilterTypeEnum;
69+
import org.zephyrsoft.sdb2.model.PresentCommandResult;
6970
import org.zephyrsoft.sdb2.model.ScreenContentsEnum;
7071
import org.zephyrsoft.sdb2.model.SelectableDisplay;
7172
import org.zephyrsoft.sdb2.model.Song;
@@ -1474,51 +1475,66 @@ protected void handleSongPresent() {
14741475
*/
14751476
protected void presentSong(Song song, SongPresentationPosition presentationPosition) {
14761477
// not in a "contentChange" block because else the sections wouldn't be displayed:
1477-
boolean success = controller.present(new Presentable(song, null), presentationPosition);
1478+
PresentCommandResult result = controller.present(new Presentable(song, null), presentationPosition);
14781479

14791480
controller.contentChange(() -> {
14801481
controller.stopSlideShow();
1481-
if (success) {
1482-
clearSectionButtons();
1483-
if (controller.hasParts()) {
1484-
List<AddressablePart> parts = controller.getParts();
1485-
Boolean showTitle = settingsModel.get(SettingKey.SHOW_TITLE, Boolean.class);
1486-
int partIndex = showTitle ? 0 : 1;
1487-
for (AddressablePart part : parts) {
1488-
PartButtonGroup buttonGroup = new PartButtonGroup(part, partIndex, controller, this);
1489-
panelSectionButtons.add(buttonGroup, panelSectionButtonsHints);
1490-
listSectionButtons.add(buttonGroup);
1491-
partIndex++;
1482+
if (result.wasSuccessful() && result.wasStateChanged()) {
1483+
btnJumpToPresented.setEnabled(true);
1484+
Boolean showTitle = settingsModel.get(SettingKey.SHOW_TITLE, Boolean.class);
1485+
1486+
if (result != PresentCommandResult.ONLY_SCOLLED) {
1487+
clearSectionButtons();
1488+
if (controller.hasParts()) {
1489+
List<AddressablePart> parts = controller.getParts();
1490+
int partIndex = showTitle ? 0 : 1;
1491+
for (AddressablePart part : parts) {
1492+
PartButtonGroup buttonGroup = new PartButtonGroup(part, partIndex, controller, this);
1493+
panelSectionButtons.add(buttonGroup, panelSectionButtonsHints);
1494+
listSectionButtons.add(buttonGroup);
1495+
partIndex++;
1496+
}
14921497
}
1498+
}
14931499

1500+
if (controller.hasParts()) {
14941501
// mark active line
14951502
if (!listSectionButtons.isEmpty()) {
1496-
listSectionButtons
1497-
.get(presentationPosition != null && presentationPosition.getPartIndex() != null
1498-
? Math.max(0, presentationPosition.getPartIndex() - (showTitle ? 0 : 1))
1499-
: 0)
1500-
.setActiveLine(presentationPosition != null && presentationPosition.getLineIndex() != null
1501-
? presentationPosition.getLineIndex()
1502-
: 0);
1503+
int activePartIndex = presentationPosition != null && presentationPosition.getPartIndex() != null
1504+
? Math.max(0, presentationPosition.getPartIndex() - (showTitle ? 0 : 1))
1505+
: 0;
1506+
int activeLineIndex = presentationPosition != null && presentationPosition.getLineIndex() != null
1507+
? presentationPosition.getLineIndex()
1508+
: 0;
1509+
1510+
for (int partIndex = 0; partIndex < listSectionButtons.size(); partIndex++) {
1511+
if (partIndex == activePartIndex) {
1512+
listSectionButtons.get(partIndex).setActiveLine(activeLineIndex);
1513+
} else {
1514+
listSectionButtons.get(partIndex).setInactive();
1515+
}
1516+
}
1517+
15031518
}
1519+
}
15041520

1505-
// add empty component to consume any space that is left (so the parts appear at the top of the
1506-
// scrollpane view)
1521+
if (result != PresentCommandResult.ONLY_SCOLLED) {
1522+
// add empty component to consume any space that is left
1523+
// (so the parts appear at the top of the scrollpane view)
15071524
panelSectionButtons.add(new JLabel(""), panelSectionButtonsLastRowHints);
15081525

15091526
panelSectionButtons.revalidate();
15101527
panelSectionButtons.repaint();
1511-
btnJumpToPresented.setEnabled(true);
15121528
}
15131529
}
15141530
});
15151531
}
15161532

15171533
protected void handleBlankScreen() {
15181534
controller.contentChange(() -> {
1519-
boolean success = controller.present(BLANK_SCREEN, null);
1535+
PresentCommandResult result = controller.present(BLANK_SCREEN, null);
15201536
controller.stopSlideShow();
1521-
if (success) {
1537+
if (result.wasSuccessful()) {
15221538
clearSectionButtons();
15231539
btnJumpToPresented.setEnabled(false);
15241540
}
@@ -1527,9 +1543,9 @@ protected void handleBlankScreen() {
15271543

15281544
protected void handleLogoPresent() {
15291545
controller.contentChange(() -> {
1530-
boolean success = controller.present(new Presentable(null, controller.loadLogo()), null);
1546+
PresentCommandResult result = controller.present(new Presentable(null, controller.loadLogo()), null);
15311547
controller.stopSlideShow();
1532-
if (success) {
1548+
if (result.wasSuccessful()) {
15331549
clearSectionButtons();
15341550
btnJumpToPresented.setEnabled(false);
15351551
}
@@ -2306,10 +2322,10 @@ public void keyReleased(KeyEvent e) {
23062322
menuPresentSelectedSong.setRenderer(new TextLineCellRenderer(btnPresentSelectedSong));
23072323
menuPresentSelectedSong.setMaximumRowCount(50);
23082324
menuPresentSelectedSong.addActionListener(actionEvent -> {
2309-
NamedSongPresentationPosition nssp = (NamedSongPresentationPosition) menuPresentSelectedSong.getSelectedItem();
2325+
NamedSongPresentationPosition nspp = (NamedSongPresentationPosition) menuPresentSelectedSong.getSelectedItem();
23102326

2311-
if (nssp != null && nssp.getPartIndex() != null) {
2312-
present(presentListSelected, nssp);
2327+
if (nspp != null && nspp.getPartIndex() != null) {
2328+
present(presentListSelected, nspp);
23132329
}
23142330

23152331
if (menuPresentSelectedSong.getModel().getSize() > 0) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.zephyrsoft.sdb2.model;
2+
3+
public enum PresentCommandResult {
4+
SUCCESS(true, true),
5+
ONLY_SCOLLED(true, true),
6+
NOTHING_TO_DO(true, false),
7+
FAILURE(false, true);
8+
9+
private final boolean successful;
10+
private final boolean stateChanged;
11+
12+
PresentCommandResult(boolean successful, boolean stateChanged) {
13+
this.successful = successful;
14+
this.stateChanged = stateChanged;
15+
}
16+
17+
public boolean wasSuccessful() {
18+
return successful;
19+
}
20+
21+
public boolean wasStateChanged() {
22+
return stateChanged;
23+
}
24+
}

src/main/java/org/zephyrsoft/sdb2/presenter/SongView.java

+23-21
Original file line numberDiff line numberDiff line change
@@ -473,28 +473,30 @@ public void moveToLine(Integer partNullable, Integer lineNullable, boolean anima
473473

474474
int part = partNullable == null ? 0 : partNullable;
475475
int line = lineNullable == null ? 0 : lineNullable;
476-
477-
adjustHeightIfNecessary();
478-
try {
479-
int noTitlePart = showTitle ? part : Math.max(part - 1, 0);
480-
AddressablePart addressablePart = parts.get(noTitlePart);
481-
Preconditions.checkArgument(addressablePart != null, "part index does not correspond to a part of the song");
482-
AddressableLine addressableLine = addressablePart.get(line);
483-
Preconditions.checkArgument(addressableLine != null, "line index does not correspond to a line of the addressed part");
484-
double targetY = getTargetYOr0(partNullable, lineNullable, addressableLine.getPosition());
485-
if (minimalScrolling && targetY > text.getPreferredSize().getHeight() + topMargin + bottomMargin - getSize().getHeight()) {
486-
targetY = text.getPreferredSize().getHeight() + topMargin + bottomMargin - getSize().getHeight();
487-
}
488-
Point targetLocation = new Point((int) text.getLocation().getX(), (int) Math.min(0, (topMargin - targetY)));
489-
LOG.debug("moving to part {} / line {} (animated={}) - target={}", part, line, animated, targetLocation);
490-
if (animated) {
491-
animatedMoveTo(targetLocation);
492-
} else {
493-
abruptMoveTo(targetLocation);
476+
477+
SwingUtilities.invokeLater(() -> {
478+
adjustHeightIfNecessary();
479+
try {
480+
int noTitlePart = showTitle ? part : Math.max(part - 1, 0);
481+
AddressablePart addressablePart = parts.get(noTitlePart);
482+
Preconditions.checkArgument(addressablePart != null, "part index does not correspond to a part of the song");
483+
AddressableLine addressableLine = addressablePart.get(line);
484+
Preconditions.checkArgument(addressableLine != null, "line index does not correspond to a line of the addressed part");
485+
double targetY = getTargetYOr0(partNullable, lineNullable, addressableLine.getPosition());
486+
if (minimalScrolling && targetY > text.getPreferredSize().getHeight() + topMargin + bottomMargin - getSize().getHeight()) {
487+
targetY = text.getPreferredSize().getHeight() + topMargin + bottomMargin - getSize().getHeight();
488+
}
489+
Point targetLocation = new Point((int) text.getLocation().getX(), (int) Math.min(0, (topMargin - targetY)));
490+
LOG.debug("moving to part {} / line {} (animated={}) - target={}", part, line, animated, targetLocation);
491+
if (animated) {
492+
animatedMoveTo(targetLocation);
493+
} else {
494+
abruptMoveTo(targetLocation);
495+
}
496+
} catch (BadLocationException e) {
497+
throw new IllegalStateException("could not identify position in text", e);
494498
}
495-
} catch (BadLocationException e) {
496-
throw new IllegalStateException("could not identify position in text", e);
497-
}
499+
});
498500
}
499501

500502
private double getTargetYOr0(Integer partNullable, Integer lineNullable, Integer addressableLinePosition)

src/main/java/org/zephyrsoft/sdb2/remote/MQTT.java

+13-15
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public class MQTT implements MqttCallback {
3434

3535
private static final Logger LOG = LoggerFactory.getLogger(MQTT.class);
3636

37-
private MqttClient client;
38-
private CopyOnWriteArrayList<OnMessageListener> onMessageListeners = new CopyOnWriteArrayList<>();
39-
private CopyOnWriteArrayList<OnConnectionLostListener> onConnectionLostListeners = new CopyOnWriteArrayList<>();
37+
private final MqttClient client;
38+
private final CopyOnWriteArrayList<OnMessageListener> onMessageListeners = new CopyOnWriteArrayList<>();
39+
private final CopyOnWriteArrayList<OnConnectionLostListener> onConnectionLostListeners = new CopyOnWriteArrayList<>();
4040

4141
public MQTT(String serverUri, String userName, String password, boolean cleanSession) throws MqttException {
4242
this(serverUri, UUID.randomUUID().toString(), userName, password, cleanSession);
@@ -46,10 +46,8 @@ public MQTT(String serverUri, String userName, String password, boolean cleanSes
4646
* A simple MQTTClient wrapper which establishes the connection on object creation,
4747
* implements the observable pattern to let multiple Observers receive messages and
4848
* handles some exceptions.
49-
*
49+
* <p>
5050
* It currently only supports String messages.
51-
*
52-
* @throws MqttException
5351
*/
5452
public MQTT(String serverUri, String clientID, String userName, String password, boolean cleanSession) throws MqttException {
5553
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
@@ -74,11 +72,9 @@ public void connectionLost(Throwable cause) {
7472
}
7573

7674
@Override
77-
public void messageArrived(String topic, MqttMessage message) throws Exception {
75+
public void messageArrived(String topic, MqttMessage message) {
7876
LOG.debug("Got message: {}", topic);
79-
onMessageListeners.forEach(oml -> {
80-
oml.onMessage(topic, message.getPayload());
81-
});
77+
onMessageListeners.forEach(oml -> oml.onMessage(topic, message.getPayload()));
8278
}
8379

8480
@Override
@@ -99,7 +95,7 @@ public void publish(String topic, byte[] payload, int qos, boolean retained) {
9995
LOG.debug("Publishing message: {}", topic);
10096
try {
10197
client.publish(topic, payload, qos, retained);
102-
LOG.debug("Payload: " + new String(payload, StandardCharsets.UTF_8));
98+
LOG.debug("Payload: {}", new String(payload, StandardCharsets.UTF_8));
10399
} catch (Exception e) {
104100
// only log the exception
105101
LOG.warn("could not publish message", e);
@@ -111,13 +107,13 @@ public void close() {
111107
if (client.isConnected()) {
112108
try {
113109
client.disconnect();
114-
} catch (MqttException e) {
110+
} catch (MqttException _) {
115111
// do nothing
116112
}
117113
}
118114
try {
119115
client.close();
120-
} catch (MqttException e) {
116+
} catch (MqttException _) {
121117
// do nothing
122118
}
123119
}
@@ -126,15 +122,17 @@ public void onMessage(OnMessageListener onMessageListener) {
126122
onMessageListeners.add(onMessageListener);
127123
}
128124

125+
@FunctionalInterface
129126
public interface OnMessageListener {
130-
public abstract void onMessage(String topic, byte[] message);
127+
void onMessage(String topic, byte[] message);
131128
}
132129

133130
public void onConnectionLost(OnConnectionLostListener onConnectionLostListener) {
134131
onConnectionLostListeners.add(onConnectionLostListener);
135132
}
136133

134+
@FunctionalInterface
137135
public interface OnConnectionLostListener {
138-
public abstract void onConnectionLost(Throwable cause);
136+
void onConnectionLost(Throwable cause);
139137
}
140138
}

0 commit comments

Comments
 (0)