NSRulerView und NSRulerMarker

Ich wollte für ein kleines Demoprogram (für  grafische Eingabe) Lineale und Guides hinzufügen, ähnlich wie bei Photoshop und anderen Programmen. Guides sind Hilfslinien, die Linealen hinzugefügt werden, um Objekte an diesen Hilfslinien auszurichten.

Nun hab ich dabei die Gelegenheit genutzt, meine Erkenntnisse bei der Implementierung der Guides zu dokumentieren.

NSRulerView-Screen

Apple hat Lineale bereits bei der Benutzung von NSScrollView vorgesehen und sie lassen sich mit relativ wenig Code aktivieren. Dabei werden Lineale über die Instanzen der Klasse NSRulerView dargestellt.

[scrollView setHasHorizontalRuler:YES];
[scrollView setHasVerticalRuler:YES];

[scrollView setRulersVisible:YES];

Dabei lassen sich Lineare in drei Zonen unterteilen. Zunächst gibt es einen Bereich in welchem das Lineal bzw. die Skala dargestellt wird, gefolgt von einem Bereich in welchem die Marker dargestellt werden. Weiterhin gibt es noch einen Bereich für einen sogenannten Accessory View. Der Accessory View kann benutzt werden um verschiedene Steuerelemente unterzubringen. Ein Beispiel wäre ein Button um alle Hilfslinien zu löschen.

NSRulerView3

Für die Marker benutzt Apple die Klasse NSRulerMarker und sie lassen sich direkt zu einer NSRulerView Instanz hinzufügen. Zur Darstellung benötigt die Klasse einen NSRulerMarker, ein NSImage sowie einen Ursprungspunkt, der die Grafik im Verhältnis zur Position des Markers positioniert.

NSImage *markerImage = [self horizontalMarkerImage];
NSRulerMarker *rulerMarker = [[NSRulerMarker alloc] initWithRulerView:horizontalRuler markerLocation:location image:markerImage imageOrigin:NSMakePoint(markerImage.size.width / 2.0f, markerImage.size.height / 2.0f)];
[scrollView.horizontalRulerView addMarker:rulerMarker];
[rulerMarker release];

Für die Manipulation der Marker benutzt der Ruler View eine Art Delegate Pattern, wobei sich der Delegate beim Ruler ViewClient View“ nennt. Der Client View ist beim Scroll View in der Regel der Document View und lässt sich wie folgt anmelden:

[scrollView.horizontalRulerView setClientView:self.scrollView.documentView];
[scrollView.verticalRulerView setClientView:self.scrollView.documentView];

Für die Funktionen „Bewegen“, „Entfernen“ und „Hinzufügen“ gibt es eine Reihe von Delegate-Methoden, welche der Client View implementieren kann. Dabei gruppieren sich diese Methoden in drei Gruppen jeweils für das Bewegen, das Entfernen und das Hinzufügen der Marker. Folgende Methoden existieren z.B. für das Bewegen der Marker.

  • rulerView:shouldMoveMarker:
  • rulerView:willMoveMarker:toLocation:
  • rulerView:didMoveMarker:

Dabei gibt es eine Methode, die entscheidet ob die jeweilige Aktion überhaupt möglich ist. Es gibt eine Methode, die über den Fortschritt der Aktion informiert und eine Methode, die das Ende der Aktion signalisiert.

Ich habe mir die Mühe gemacht zu analysieren, in welcher Reihenfolge die Methoden aufgerufen werden und habe dies in einem Sequenzdiagramm dargestellt. Zunächst habe ich analysiert was passiert, wenn ich versuche einen Marker zu bewegen. Dabei ist mir aufgefallen, dass Marker nur bewegt werden können, wenn man mit der Maus in dem Marker-Bereich des Ruler View klickt. Das ist leider eine unpraktische Eigenschaft, da ich versucht habe den Marker im Lineal-Bereich anzuzeigen.

Wenn ich nun in den Marker-Bereich klicke, sucht der Ruler View in den gegebenen Markern nach einem Treffer und ruft mit dem gefundenen Marker die eigene Methode trackMarker:withMouseEvent: auf. Die Methode ruft wiederum die Methode trackMouse:adding: von diesem Marker auf und organisiert die weitere Kommunikation mit dem Client View. Vermutlich verwendet der Marker eine modale Event-Verfolgungsschleife (Mouse-Tracking Loop Approach).

Um festzustellen ob der Marker überhaupt bewegt werden darf wird rulerView:shouldMoveMarker: vom Client View ausgeführt und nur wenn diese Methode YES liefert, lässt sich der Marker bewegen. Sobald nun die Mouse bewegt wird, wird der Client View über die Methode rulerView:willMoveMarker: informiert. Interessant ist der Rückgabewert der Methode. Mit dem Rückgabewert kann der Client View Einfluss auf die neue Position nehmen, z.B. um nur diskrete Positionen zu erlauben.

Sobald nun die Maus losgelassen wird, ist die Bewegung beendet und die Methode rulerView:didMoveMarker: wird aufgerufen. Hier lassen sich nun Aktionen mit der neuen Position des Markers durchführen.

NSRulerView2

Das Hinzufügen von Markern funktioniert ein wenig anders, ist aber insgesamt recht ähnlich. Eine Möglichkeit für das Hinzufügen ist über einen Klick in den Marker-Bereich. Nachdem in den Bereich geklickt wird und der Ruler View keinen Marker für diese Position gefunden hat, ruft der View die Methode rulerView:handleMouseDown: vom Client View auf. Diese Methode gibt dem Client View die Möglichkeit auf Maus-Ereignisse einzugehen. Eine weitere Möglichkeit ist, einen Marker manuell zu instanzieren und zu dem Markern des Ruler Views hinzuzufügen.

Der geplante Weg ist vermutlich, dass der Client View den Ruler View aufruft damit dieser den Marker verwaltet. Ab dem Moment gleicht die Sequenz der Vorherigen.

NSRulerView1

Bei meiner Suche nach der Funktionsweise der Ruler Views bin ich auf ein schönen Artikel über die Darstellung von Zeilennummern im Ruler View gestoßen: „Displaying Line Numbers with NSTextView“.

Back