2D-Grafik Verbesserungen an Hot Corner Funktionalität?

DStrohma

Bekanntes Mitglied
Zuersteinmal: Unter Hot Corner Funktionalität verstehe ich die Möglichkeit dass der User die Maus in eine der vier Bildschirm-Ecken zieht und daraufhin eine frei wählbare Funktionalität ausgeführt wird.

Ich brauche das für eine Software die ich schreibe. Da ich keine Bibliotheken gefunden habe die das können, habe ich das selbst umgesetzt. Grundsätzlich funktioniert alles so wie ich es will. Derzeitige Funktionalität:
  • Eine Klasse kann ein Interface implementieren und somit bestimmen was passieren soll wenn der Mauszeiger in eine Ecke gezogen wird
  • Es können wenn gewünscht grundsätzlich all vier Ecken registriert werden
  • Der Radius der Ecke kann angepasst werden
  • Es kann eine animierte / transparente Grafik in die Ecke eingeblendet und ausgeblendet werden wenn die Hot Corner Aktivität ausgeführt wird
  • Die Java Anwendung muss nicht sichtbar sein damit die Hot Corners funktionieren
Aber es gibt auch ein paar unschöne Dinge an dem ganzen die ich bisher noch nicht beseitigt habe:
  • Wenn eine Ecke registriert wird, wird jedesmal ein neuer Thread gestartet der die Maus überwacht. Es wäre aber besser wenn sich ein einziger Thread um alle registrierten Ecken kümmern würde
  • Derzeit ist das anzeigen der Grafik nur in der linken oberen Ecke möglich
  • Die Grafiken werden aus Dateien geladen (GIF / PNG). Diese Funktionalität würde ich gerne behalten aber es wäre klasse wenn es auch möglich wäre eine in Java erstellte Grafik (änlich der Form von "corner.png" zu verwenden)
  • Grundsätzlich kommt mir die Implementierung wie sie jetzt ist unnötig komplex und kompliziert vor, ließe sich das verbessern?
  • Das Interface hat derzeit nur eine Methode und deshalb kann momentan nur eine Funktionalität hinterlegt werden, selbst wenn man alle 4 Bildschirmecken registriert. Ich könnte das Interface natürlich erweitern aber irgendwie kommt mir das alles unnötig kompliziert vor. Kann man hier nicht irgendwie vielleicht AbstractActions verwenden? Wenn ich bei dem Interface bleibe, dann muss in Zukunft jede Klasse alle vier Methoden implementieren selbst wenn sie nur eine nutzt...

Worüber ich mich freuen würde: Wenn sich jemand der einen Verbesserungsvorschlag hat melden würde und mir zeigt wie ich es besser machen kann. Wichtig wäre mir auch das Thema Performance (da gibt es derzeit keine Probleme aber vielleicht kann man das trotzdem noch verbessern?).

Hier ein lauffähiges Beispiel in zwei Klassen zusammengewürfelt bei dem ein JFrame angezeigt bzw. versteckt wird wenn man die Maus in die linke obere Ecke zieht. Hot Corner Implementierung:
Java:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Endogen on 14.02.14.
 */
public class HotCorner {
    private static final HotCorner instance = new HotCorner();
    private static Map<Corner, HotCornerInterface> owners = new HashMap<Corner, HotCornerInterface>();
    private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    private static double radius = 6;
    private static boolean switchBool = true;

    private HotCorner() {}

    public static HotCorner getInstance() {
        return instance;
    }

    public static void registerHotCorner(final Corner corner, HotCornerInterface owner) {
        owners.put(corner, owner);

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Point lastPosition = new Point();
                Point cornerPoint = new Point();

                switch (corner) {
                    case TOP_LEFT:
                        cornerPoint = new Point(0, 0);
                        break;
                    case TOP_RIGHT:
                        cornerPoint = new Point((int)screenSize.getWidth(), 0);
                        break;
                    case BOTTOM_LEFT:
                        cornerPoint = new Point(0, (int)screenSize.getHeight());
                        break;
                    case BOTTOM_RIGHT:
                        cornerPoint = new Point((int)screenSize.getWidth(), (int)screenSize.getHeight());
                        break;
                }

                while (true) {
                    Point currentPosition = MouseInfo.getPointerInfo().getLocation();
                    if (!currentPosition.equals(lastPosition)) {
                        lastPosition = currentPosition;
                        boolean inside = isInside(currentPosition, cornerPoint);
                        if (inside && switchBool) {
                            HotCornerInterface owner = owners.get(corner);
                            if (owner != null) {
                                owner.onHotCorner();
//                                showImage();
                            } else {
                                return;
                            }
                            switchBool = false;
                        }
                        if (!inside) {
                            switchBool = true;
                        }
                    }
                }
            }
        };

        new Thread(runnable).start();
    }

    private static boolean isInside(Point currentPosition, Point cornerPoint) {
        double x = cornerPoint.getX() - (radius/2);
        double y = cornerPoint.getY() - (radius/2);
        Ellipse2D circle = new Ellipse2D.Double(x, y, radius, radius);

        if (circle.contains(currentPosition.getX(), currentPosition.getY())) {
            return true;
        }

        return false;
    }

    private static void showImage() {
        final JWindow frame = new JWindow();
        frame.setAlwaysOnTop(true);
        frame.setBackground(new Color(0f, 0f, 0f, 0.0f));
        frame.setOpacity(0);
        frame.add(new JLabel(new ClearImageIcon("corner.png")));
        frame.pack();
        frame.setVisible(true);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (frame.getOpacity() <= 0.90F) {
                        Thread.sleep(10);
                        frame.setOpacity(frame.getOpacity() + 0.05F);
                    }
                    Thread.sleep(800);
                    while (frame.getOpacity() >= 0.04F) {
                        Thread.sleep(20);
                        frame.setOpacity(frame.getOpacity() - 0.02F);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                frame.dispose();
            }
        }).start();
    }

    public static void unregisterHotCorner(Corner corner) {
        owners.remove(corner);
    }

    public enum Corner {
        TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT;
    }

    public static void setRadius(int radius) {
        HotCorner.radius = radius;
    }

    public static class ClearImageIcon extends ImageIcon {

        public ClearImageIcon(String filename) {
            super(filename);
        }

        @Override
        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
            Graphics2D g2 = (Graphics2D)g.create();
            g2.setBackground(new Color(0,0,0,0));
            g2.clearRect(0, 0, getIconWidth(), getIconHeight());
            super.paintIcon(c, g2, x, y);
        }
    }

    public interface HotCornerInterface {
        public void onHotCorner();
    }
}

Test JFrame:
Java:
import javax.swing.*;
import java.awt.*;

/**
 * Created by Endogen on 15.02.14.
 */
public class TestFrame extends JFrame implements HotCorner.HotCornerInterface {

    public static void main(String[] args) {
        new TestFrame().setVisible(true);
    }

    public TestFrame() {
        setPreferredSize(new Dimension(400, 500));
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        pack();
        setLocationRelativeTo(null);

        HotCorner.getInstance().registerHotCorner(HotCorner.Corner.TOP_LEFT, this);
    }

    @Override
    public void onHotCorner() {
        if (TestFrame.this.isVisible()) {
            TestFrame.this.setVisible(false);
        } else {
            TestFrame.this.setVisible(true);
            TestFrame.this.setState(JFrame.NORMAL);
            TestFrame.this.toFront();
            TestFrame.this.repaint();
        }
    }
}

Ich habe die Darstellung der Grafik auskommentiert damit es keine Probleme gibt mit irgendwelchen Pfaden usw. aber wer will, kann sie gerne einkommentieren und sich z.B. diese Grafik hier holen um sie darzustellen: http://s28.postimg.org/xlyf58yl5/corner.png oder aber irgendeine animierte GIF verwenden. Dann die Datei direkt in das Projektverzeichnis legen.
 
Zuletzt bearbeitet:

JavaMeister

Gesperrter Benutzer
Keine threadsichere implementierung eines Singletons.

Ich sehe gerade: Das ist ALLES static? Wofür überhaupt die getInstance() Methode? Also die Vor und Nachteile herauszusuchen überlasse ich Dir ;D


while (true) {

kriegt man den Thread nie in den Selbstmord. Wieso dann pro Listener ein Thread startet, ist nicht nachvollziehbar. Vor allen Dingen, wer denkt, dass er mehrere Listener pro Ecke registrieren kann, der erzeugt haufenweise Threads, die dann mehrfach den aktuellen Listener benachrichtigen ;D.

Des Weiteren: Deine Map verwaltet exakt nur 4 Ecken. Wenn ich nun in eine Ecke 2 Listener packen möchte, dann überschreibe ich den letzten.

Funktioniert das auch an Multi-Monitor Systemen? Glaube nub.

HotCorner.getInstance().registerHotCorner(HotCorner.Corner.TOP_LEFT, this);

Hier müsste ne eindeutige Warnung kommen.

Ist das gleiche wie: HotCorner.registerHotCorner(HotCorner.Corner.TOP_LEFT, this);

---

TestFrame ist ein Frame und implementiert HotCornerInterface

Sei jedem selbst überalassen. Aber entweder bin ich ein Frame oder ein HotCornerInterface. Wobei die benennung maxmial schlecht gewählt ist.

---

TestFrame.this.isVisible() Muss ich sagen sehe ich in diesem Kontext das erste mal. In Anonymen Innerclasses macht das sinn. Hier nicht.

---

Der schlimmste Fehler, der hier begannen worden ist: Hauptsache es funktioniert.

Tja. Funktioniert genau für deine Anwendung. Genau für den einen registrierten Corner.

Dir fehlen elementare Kentnisse der objektorientierten Programmierung.

---

Registriert seit: 14.06.2008

Aber nicht durchgehend in diesem Thema drin oder?

---

Sorry, vermutlich auch nicht unbedingt das, was du lesen wolltest. Die Idee ist gut. Aber darum geht es hier nicht.
 
Zuletzt bearbeitet:

DStrohma

Bekanntes Mitglied
Keine threadsichere implementierung eines Singletons.

Ich sehe gerade: Das ist ALLES static? Wofür überhaupt die getInstance() Methode? Also die Vor und Nachteile herauszusuchen überlasse ich Dir ;D
Stimmt, nicht Thread sicher, muss ich noch ändern. Huch, stimmt, alles static :oops: ist natürlich unsinnig.

while (true) {

kriegt man den Thread nie in den Selbstmord. Wieso dann pro Listener ein Thread startet, ist nicht nachvollziehbar. Vor allen Dingen, wer denkt, dass er mehrere Listener pro Ecke registrieren kann, der erzeugt haufenweise Threads, die dann mehrfach den aktuellen Listener benachrichtigen ;D.
Wie ich schon geschrieben habe muss das noch geändert werden. Ist mir auch aufgefallen dass das Schrott ist :D

Des Weiteren: Deine Map verwaltet exakt nur 4 Ecken. Wenn ich nun in eine Ecke 2 Listener packen möchte, dann überschreibe ich den letzten.
Das war von mir ursprünglich auch so gewollt aber wenn ich jetzt so drüber nachdenke, wieso nur einer Klasse die Möglichkeit geben eine bestimmte Aktion auszuführen? Hast recht, macht keinen Sinn, werde ich ändern. Danke!

Funktioniert das auch an Multi-Monitor Systemen? Glaube nub.
So wie es jetzt ist, sollte es die Gesamt-Fläche aller Monitore liefern und verwalten. Also das sollte so passen. Ich will nicht pro Monitor 4 Ecken verwalten, sondern pro Gesamtbereich aller Monitore 4 Ecken.

HotCorner.getInstance().registerHotCorner(HotCorner.Corner.TOP_LEFT, this);

Hier müsste ne eindeutige Warnung kommen.

Ist das gleiche wie: HotCorner.registerHotCorner(HotCorner.Corner.TOP_LEFT, this);
Das Thema hatten wir schon, das alles static ist, ist Schrott, gebe ich zu :)

TestFrame ist ein Frame und implementiert HotCornerInterface

Sei jedem selbst überalassen. Aber entweder bin ich ein Frame oder ein HotCornerInterface. Wobei die benennung maxmial schlecht gewählt ist.
Das verstehe ich jetzt nicht ganz. Was ist an dem Namen "HotCornerInterface" falsch? Kannst du mir das nochmal etwas genauer erklären?

TestFrame.this.isVisible() Muss ich sagen sehe ich in diesem Kontext das erste mal. In Anonymen Innerclasses macht das sinn. Hier nicht.
Stimmt, ist überhaupt nicht notwendig. :oops:

Der schlimmste Fehler, der hier begannen worden ist: Hauptsache es funktioniert.

Tja. Funktioniert genau für deine Anwendung. Genau für den einen registrierten Corner.

Dir fehlen elementare Kentnisse der objektorientierten Programmierung.
:D Ich kann natürlich nachvollziehen wieso du das sagst. Die Sache ist die, ich habe das nicht als abgeschlossen angesehen, sondern wollte - bevor ich das weiter ausbaue - etwas Feedback einholen ob das so in der Richtung Sinn macht oder ich grundlegend den falschen Ansatz gewählt habe. Z.B. dass es nur für einen Corner geht, habe ich in meiner Beschreibung schon angegeben, genauso wie auch die Tatsache dass jedes mal ein neuer Thread erzeugt wird und das total unnötig ist. Das ist mir alles bewusst und wird natürlich behoben.

Registriert seit: 14.06.2008

Aber nicht durchgehend in diesem Thema drin oder?
Haha :toll: Ob du es glaubst oder nicht ich verdiene meinen Lebensunterhalt damit - ich bin Software Entwickler (Java) aber habe eine proprietäre SDK in der ich arbeite und dabei habe ich ehrlich gesagt wenig mit normalem Java-Wahnsinn zutun.

Sorry, vermutlich auch nicht unbedingt das, was du lesen wolltest. Die Idee ist gut. Aber darum geht es hier nicht.
Doch eigentlich schon :) Ich wollte sicher gehen dass der Ansatz (und um nichts anderes handelt es sich hier) soweit Sinn macht. Ich wollte nicht irgendwas fertig implementieren um am Ende festzustellen dass es anders viel besser und einfacher (performanter?) geht.
Ich hätte vor allem gerne noch folgende Fragen beantwortet: Kann ich die Interface Geschichte irgendwie umgehen? Ich würde bei dem Aufruf
Java:
HotCorner.getInstance().registerHotCorner(HotCorner.Corner.TOP_LEFT, this);
lieber eine AbstractAction übergeben als ein Interface zu implementieren.
Des weiteren stört mich - wie ich auch schon geschrieben habe - dass wenn ich das für alle 4 Ecken umsetze, die Klasse die das Interface implementiert 4 Methoden implementieren muss. Finde ich unsinnig wenn ich z.B. nur eine Ecke verwenden will. Weis grad noch nicht wie das mit diesen Adaptern funktioniert, vielleicht kann ich es so machen wie z.B. beim MouseListener / MouseAdapter?

Danke für deine Kommentare, grundsätzlich sehr hilfreich.
 

Ruzmanz

Top Contributor
So wie es jetzt ist, sollte es die Gesamt-Fläche aller Monitore liefern und verwalten. Also das sollte so passen. Ich will nicht pro Monitor 4 Ecken verwalten, sondern pro Gesamtbereich aller Monitore 4 Ecken.

Hätte mich jetzt stark gewundert, wenn das tatsächlich der Fall wäre. Habe den Quelltext nur überflogen.

Toolkit.getScreenSize()
Gets the size of the screen. On systems with multiple displays, the primary display is used. Multi-screen aware display dimensions are available from GraphicsConfiguration and GraphicsDevice.

Selbst wenn das gelöst ist. Was passiert wenn deine Applikation nur auf einen Bildschirm läuft und der zweite ignoriert wird? Ist oft der Fall.

-----

Wenn du das ganze schon auf AWT / Swing beschränkst: Warum nutzt du nicht einfach den MouseMotionListener ... so sind keine weiteren Threads notwenig ... oder erweiterst die AWTListener. Dazu habe ich irgendwo ein ganz nettes Tutorial gesehen.

-----

Ist es tatsächlich sinnvoll so viel Logik in deinem Listener zu bauen? Evtl. braucht jemand eine höhere Flexibilität und deshalb Basismethoden wie ...

Java:
enteredCorner() {
  if(evt.getCorner() == Coner.RIGHT) {
    // Animation "Menü einblenden"
    // MenüRechts visible
  }
}

leftCorner() {
  if(evt.getCorner() == Coner.RIGHT) {
    // Animation "ausblenden"
    // MenüRechts invisible
  }
}
 

DStrohma

Bekanntes Mitglied
Hätte mich jetzt stark gewundert, wenn das tatsächlich der Fall wäre. Habe den Quelltext nur überflogen.



Selbst wenn das gelöst ist. Was passiert wenn deine Applikation nur auf einen Bildschirm läuft und der zweite ignoriert wird? Ist oft der Fall.
Hm, ich hatte eigentlich angenommen dass Toolkit.getScreenSize() mir die Gesamt-Größe liefert aber dem ich dann wohl nicht so ;( Ok, muss ich dann anders machen. Und stimmt, ist ein guter Punkt, was wenn einer der anderen Monitore garnicht verwendet werden soll... Ok, ich sehe schon, da muss ich mich mehr damit beschäftigen und das Multi-Monitor sicher machen :)
Meine Grundannahme war: Wenn ich zwei Monitore habe die nebeneinander stehen, dann muss ich die rechten Ecken des linken Monitors nicht beachten weil das einfach keinen Sinn macht. Das sind für mich keine Ecken, sondern die Mitte des Bildes - aber was wenn die Monitore übereinander stehen... usw. Also danke für die Überzeugungsarbeit ^^

Wenn du das ganze schon auf AWT / Swing beschränkst: Warum nutzt du nicht einfach den MouseMotionListener ... so sind keine weiteren Threads notwenig ... oder erweiterst die AWTListener. Dazu habe ich irgendwo ein ganz nettes Tutorial gesehen.
Was gibt es denn sonst außer AWT / Swing? Und wieso beschränke ich mir hier derzeit? Kläre mich bitte auf wenn möglich :) Ich kann den MouseMotionListener nicht verwenden da er zu "ungenau" ist. Bedeutet, wenn ich die Maus zu schnell bewege, checkt er nicht dass ich die Ecke bereits erreicht habe (wenn ich sie Maus schnell hin und schnell wieder weg bewege). Außerdem geht das mit dem Listener nur, wenn die Maus innerhalb der laufenden Anwendung ist.

Ist es tatsächlich sinnvoll so viel Logik in deinem Listener zu bauen? Evtl. braucht jemand eine höhere Flexibilität und deshalb Basismethoden wie ...

Java:
enteredCorner() {
  if(evt.getCorner() == Coner.RIGHT) {
    // Animation "Menü einblenden"
    // MenüRechts visible
  }
}

leftCorner() {
  if(evt.getCorner() == Coner.RIGHT) {
    // Animation "ausblenden"
    // MenüRechts invisible
  }
}
Soweit will ich das ehrlich gesagt garnicht runterbrechen. Ich brauche diese Flexibilität einfach nicht und die Animation soll immer nur angezeigt werden wenn die Ecke berührt wird und die dazu passende Aktion ausgeführt wird. Beim verlassen der Ecke soll keine Animation angezeigt werden.
Grundsätzlich soll das so funktionieren wie bei GNOME 3.10.

Danke!
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben