GUI: added changeable card popup mode for chats/logs by card name clicks (alternative to mouse wheel from game cards);

other: fixed duplicated chat popups in game, added miss error logs from popup related code, added additional checks for good code usage;
This commit is contained in:
Oleg Agafonov 2023-11-26 12:48:41 +04:00
parent 785f6973b9
commit 4500b79008
12 changed files with 129 additions and 70 deletions

View file

@ -2,7 +2,6 @@ package mage.cards.action;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
public interface ActionCallback {
@ -20,7 +19,7 @@ public interface ActionCallback {
void mouseExited(MouseEvent e, TransferData data);
void mouseWheelMoved(MouseWheelEvent e, TransferData data);
void mouseWheelMoved(int mouseWheelRotation, TransferData data);
void hideOpenComponents();

View file

@ -32,7 +32,7 @@ public class EmptyCallback implements ActionCallback {
}
@Override
public void mouseWheelMoved(MouseWheelEvent e, TransferData data) {
public void mouseWheelMoved(int mouseWheelRotation, TransferData data) {
}
@Override

View file

@ -28,6 +28,7 @@ import mage.util.CardUtil;
import mage.util.MultiAmountMessage;
import mage.util.RandomUtil;
import javax.swing.*;
import java.io.File;
import java.lang.reflect.Constructor;
import java.text.DateFormat;
@ -917,4 +918,11 @@ public final class SystemUtil {
throw new IllegalArgumentException("Wrong code usage: client commands code must run in CALL threads, but used in " + name, new Throwable());
}
}
public static void ensureRunInGUISwingThread() {
if (!SwingUtilities.isEventDispatchThread()) {
// hot-to fix: run GUI changeable code by SwingUtilities.invokeLater(() -> {xxx})
throw new IllegalArgumentException("Wrong code usage: GUI related code must run in SWING thread by SwingUtilities.invokeLater", new Throwable());
}
}
}

View file

@ -1,66 +1,65 @@
package mage.utils;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import java.util.concurrent.*;
/**
* Util method to work with threads.
*
* @author ayrat
*/
@SuppressWarnings("unchecked")
public final class ThreadUtils {
public static final ThreadPoolExecutor threadPool;
public static final ThreadPoolExecutor threadPool2;
public static final ThreadPoolExecutor threadPool3;
private static final Logger logger = Logger.getLogger(ThreadUtils.class);
public static final ThreadPoolExecutor threadPoolSounds;
public static final ThreadPoolExecutor threadPoolPopups;
private static int threadCount;
static {
/**
* used in CardInfoPaneImpl
*
*/
threadPool = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
threadPoolSounds = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
threadCount++;
Thread thread = new Thread(runnable, "Util" + threadCount);
Thread thread = new Thread(runnable, "SOUND-" + threadCount);
thread.setDaemon(true);
return thread;
}
});
threadPool.prestartAllCoreThreads();
}) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
t = findRealException(r, t);
if (t != null) {
// TODO: show sound errors in client logs?
//logger.error("Catch unhandled error in SOUND thread: " + t.getMessage(), t);
}
}
};
threadPoolSounds.prestartAllCoreThreads();
/**
* Used for MageActionCallback
*/
threadPool2 = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
threadPoolPopups = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
threadCount++;
Thread thread = new Thread(runnable, "TP2" + threadCount);
Thread thread = new Thread(runnable, "POPUP-" + threadCount);
thread.setDaemon(true);
return thread;
}
});
threadPool2.prestartAllCoreThreads();
/**
* Used for Enlarged view
*/
}) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
threadPool3 = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
threadCount++;
Thread thread = new Thread(runnable, "EV" + threadCount);
thread.setDaemon(true);
return thread;
// catch errors in popup threads (example: card popup over cards or chat/log messages)
t = findRealException(r, t);
if (t != null) {
logger.error("Catch unhandled error in POPUP thread: " + t.getMessage(), t);
}
}
});
threadPool3.prestartAllCoreThreads();
};
threadPoolPopups.prestartAllCoreThreads();
}
public static void sleep(int millis) {
@ -78,4 +77,28 @@ public final class ThreadUtils {
}
}
}
/**
* Find real exception object after thread task completed. Can be used in afterExecute
*
* @param r
* @param t
* @return
*/
public static Throwable findRealException(Runnable r, Throwable t) {
// executer.submit - return exception in result
// executer.execute - return exception in t
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
return t;
}
}