範囲選択の実装。
範囲選択はできても、選択範囲に対して何か処理を行うことはできない。
また、範囲選択中のオートスクロールの実装がかなり雑。要見直し。
@@ -25,6 +25,7 @@ | ||
25 | 25 | |
26 | 26 | package com.hironytic.moltonf.view.event; |
27 | 27 | |
28 | +import java.awt.event.MouseEvent; | |
28 | 29 | import java.util.EventObject; |
29 | 30 | |
30 | 31 | import com.hironytic.moltonf.model.Link; |
@@ -38,6 +39,9 @@ | ||
38 | 39 | /** クリックされたリンク */ |
39 | 40 | private Link link; |
40 | 41 | |
42 | + /** クリックの引き金になったマウスイベント */ | |
43 | + private MouseEvent mouseEvent; | |
44 | + | |
41 | 45 | /** |
42 | 46 | * コンストラクタ |
43 | 47 | * @param source イベントが最初に発生したオブジェクト |
@@ -63,6 +67,22 @@ | ||
63 | 67 | } |
64 | 68 | |
65 | 69 | /** |
70 | + * クリックの引き金になったマウスイベントを取得します。 | |
71 | + * @return マウスイベント | |
72 | + */ | |
73 | + public MouseEvent getMouseEvent() { | |
74 | + return mouseEvent; | |
75 | + } | |
76 | + | |
77 | + /** | |
78 | + * クリックの引き金になったマウスイベントをセットします。 | |
79 | + * @param mouseEvent マウスイベント | |
80 | + */ | |
81 | + public void setMouseEvent(MouseEvent mouseEvent) { | |
82 | + this.mouseEvent = mouseEvent; | |
83 | + } | |
84 | + | |
85 | + /** | |
66 | 86 | * @see java.lang.Object#toString() |
67 | 87 | */ |
68 | 88 | @Override |
@@ -0,0 +1,46 @@ | ||
1 | +/* | |
2 | + * Moltonf | |
3 | + * | |
4 | + * Copyright (c) 2010 Project Moltonf | |
5 | + * | |
6 | + * Permission is hereby granted, free of charge, to any person obtaining | |
7 | + * a copy of this software and associated documentation files (the | |
8 | + * "Software"), to deal in the Software without restriction, including | |
9 | + * without limitation the rights to use, copy, modify, merge, publish, | |
10 | + * distribute, sublicense, and/or sell copies of the Software, and to | |
11 | + * permit persons to whom the Software is furnished to do so, subject to | |
12 | + * the following conditions: | |
13 | + * | |
14 | + * The above copyright notice and this permission notice shall be included | |
15 | + * in all copies or substantial portions of the Software. | |
16 | + * | |
17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
19 | + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
20 | + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
21 | + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
22 | + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
23 | + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
24 | + */ | |
25 | + | |
26 | +package com.hironytic.moltonf.view; | |
27 | + | |
28 | +import java.awt.geom.Point2D; | |
29 | + | |
30 | +/** | |
31 | + * 選択可能なオブジェクトのインタフェース | |
32 | + */ | |
33 | +public interface Selectable { | |
34 | + | |
35 | + /** | |
36 | + * 指定した範囲を選択します。 | |
37 | + * @param startPt 選択の開始点。 | |
38 | + * @param endPt 選択の終了点。 | |
39 | + */ | |
40 | + public void selectRange(Point2D startPt, Point2D endPt); | |
41 | + | |
42 | + /** | |
43 | + * 範囲選択をクリアします。 | |
44 | + */ | |
45 | + public void clearSelection(); | |
46 | +} |
@@ -38,10 +38,10 @@ | ||
38 | 38 | import java.awt.geom.Dimension2D; |
39 | 39 | import java.awt.geom.Ellipse2D; |
40 | 40 | import java.awt.geom.Path2D; |
41 | +import java.awt.geom.Point2D; | |
41 | 42 | import java.awt.geom.Rectangle2D; |
42 | 43 | import java.awt.geom.RoundRectangle2D; |
43 | 44 | import java.awt.image.ImageObserver; |
44 | -import java.util.ArrayList; | |
45 | 45 | import java.util.Arrays; |
46 | 46 | import java.util.Collections; |
47 | 47 | import java.util.List; |
@@ -49,7 +49,6 @@ | ||
49 | 49 | import javax.swing.JComponent; |
50 | 50 | |
51 | 51 | import com.hironytic.moltonf.model.HighlightSetting; |
52 | -import com.hironytic.moltonf.model.Link; | |
53 | 52 | import com.hironytic.moltonf.model.MessageRange; |
54 | 53 | import com.hironytic.moltonf.model.Talk; |
55 | 54 | import com.hironytic.moltonf.model.TalkType; |
@@ -59,7 +58,7 @@ | ||
59 | 58 | * 発言の表示を担当するビュー |
60 | 59 | */ |
61 | 60 | @SuppressWarnings("serial") |
62 | -public class TalkView extends JComponent implements MoltonfView { | |
61 | +public class TalkView extends JComponent implements MoltonfView, Selectable { | |
63 | 62 | |
64 | 63 | /** ビューの左の余白 */ |
65 | 64 | private static final float VIEW_PADDING_LEFT = 16; |
@@ -127,9 +126,6 @@ | ||
127 | 126 | /** 墓下メッセージの文字色 */ |
128 | 127 | private static final Color MESSAGE_TEXT_COLOR_GRAVE = new Color(0x000000); |
129 | 128 | |
130 | - /** 通常のリンクの色 */ | |
131 | - private static final Color LINK_COLOR = new Color(0xff8800); | |
132 | - | |
133 | 129 | /** 発言中のリンクの色 */ |
134 | 130 | private static final Color LINK_COLOR_IN_TALK = new Color(0xff0000); |
135 | 131 |
@@ -423,15 +419,8 @@ | ||
423 | 419 | // 発言 |
424 | 420 | talkMessageComponent.setMessage(talk.getMessageLines()); |
425 | 421 | talkMessageComponent.setForeground(getMessageTextColor()); |
426 | -// //test | |
427 | -// class DummyLink extends Link { | |
428 | -// DummyLink(MessageRange range) { super(range); } | |
429 | -// } | |
430 | -// List<MessageComponent.LinkInfo> linkInfoList = new ArrayList<MessageComponent.LinkInfo>(); | |
431 | -// linkInfoList.add(new MessageComponent.LinkInfo(new DummyLink(new MessageRange(0, 0, 2)), LINK_COLOR_IN_TALK)); | |
432 | -// talkMessageComponent.setLinkInfoList(linkInfoList); | |
433 | -// //test | |
434 | 422 | talkMessageComponent.updateLayout(areaSize.width - (VIEW_PADDING_LEFT + MESSAGE_LEFT + MESSAGE_PADDING_LEFT + MESSAGE_PADDING_RIGHT + VIEW_PADDING_RIGHT)); |
423 | + | |
435 | 424 | Dimension2D messageAreaSize = talkMessageComponent.getAreaSize(); |
436 | 425 | Rectangle2D messageAreaRect = new Rectangle2D.Float( |
437 | 426 | VIEW_PADDING_LEFT + MESSAGE_LEFT + MESSAGE_PADDING_LEFT, |
@@ -466,4 +455,55 @@ | ||
466 | 455 | talkInfoComponent.setFont(font); |
467 | 456 | talkMessageComponent.setFont(font); |
468 | 457 | } |
458 | + | |
459 | + /** | |
460 | + * 範囲選択オブジェクトをセットします。 | |
461 | + * @param rangeSelector 範囲選択オブジェクト | |
462 | + */ | |
463 | + public void setRangeSelector(RangeSelector rangeSelector) { | |
464 | + talkInfoComponent.setRangeSelector(rangeSelector); | |
465 | + talkMessageComponent.setRangeSelector(rangeSelector); | |
466 | + } | |
467 | + | |
468 | + /** | |
469 | + * @see com.hironytic.moltonf.view.Selectable#clearSelection() | |
470 | + */ | |
471 | + @Override | |
472 | + public void clearSelection() { | |
473 | + talkInfoComponent.clearSelection(); | |
474 | + talkMessageComponent.clearSelection(); | |
475 | + } | |
476 | + | |
477 | + /** | |
478 | + * @see com.hironytic.moltonf.view.Selectable#selectRange(java.awt.geom.Point2D, java.awt.geom.Point2D) | |
479 | + */ | |
480 | + @Override | |
481 | + public void selectRange(Point2D startPt, Point2D endPt) { | |
482 | + if (startPt.getY() > endPt.getY()){ | |
483 | + Point2D tempPt = endPt; | |
484 | + endPt = startPt; | |
485 | + startPt = tempPt; | |
486 | + } | |
487 | + | |
488 | + Rectangle2D infoBounds = talkInfoComponent.getBounds(); | |
489 | + Rectangle2D messageBounds = talkMessageComponent.getBounds(); | |
490 | + | |
491 | + if (startPt.getY() > infoBounds.getMaxY() || | |
492 | + endPt.getY() < infoBounds.getMinY()) { | |
493 | + talkInfoComponent.clearSelection(); | |
494 | + } else { | |
495 | + Point2D infoStartPt = ViewUtilities.convertPoint(this, startPt, talkInfoComponent); | |
496 | + Point2D infoEndPt = ViewUtilities.convertPoint(this, endPt, talkInfoComponent); | |
497 | + talkInfoComponent.selectRange(infoStartPt, infoEndPt); | |
498 | + } | |
499 | + | |
500 | + if (startPt.getY() > messageBounds.getMaxY() || | |
501 | + endPt.getY() < messageBounds.getMinY()) { | |
502 | + talkMessageComponent.clearSelection(); | |
503 | + } else { | |
504 | + Point2D messageStartPt = ViewUtilities.convertPoint(this, startPt, talkMessageComponent); | |
505 | + Point2D messageEndPt = ViewUtilities.convertPoint(this, endPt, talkMessageComponent); | |
506 | + talkMessageComponent.selectRange(messageStartPt, messageEndPt); | |
507 | + } | |
508 | + } | |
469 | 509 | } |
@@ -0,0 +1,47 @@ | ||
1 | +/* | |
2 | + * Moltonf | |
3 | + * | |
4 | + * Copyright (c) 2010 Project Moltonf | |
5 | + * | |
6 | + * Permission is hereby granted, free of charge, to any person obtaining | |
7 | + * a copy of this software and associated documentation files (the | |
8 | + * "Software"), to deal in the Software without restriction, including | |
9 | + * without limitation the rights to use, copy, modify, merge, publish, | |
10 | + * distribute, sublicense, and/or sell copies of the Software, and to | |
11 | + * permit persons to whom the Software is furnished to do so, subject to | |
12 | + * the following conditions: | |
13 | + * | |
14 | + * The above copyright notice and this permission notice shall be included | |
15 | + * in all copies or substantial portions of the Software. | |
16 | + * | |
17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
19 | + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
20 | + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
21 | + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
22 | + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
23 | + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
24 | + */ | |
25 | + | |
26 | +package com.hironytic.moltonf.view; | |
27 | + | |
28 | +import java.awt.Component; | |
29 | +import java.awt.geom.Point2D; | |
30 | + | |
31 | +import javax.swing.SwingUtilities; | |
32 | + | |
33 | +/** | |
34 | + * ビューで用いるユーティリティ群 | |
35 | + */ | |
36 | +public class ViewUtilities { | |
37 | + /** | |
38 | + * source 座標体系の aPoint を destination 座標体系に変換します。 | |
39 | + * @param source | |
40 | + * @param aPoint | |
41 | + * @param destination | |
42 | + * @return | |
43 | + */ | |
44 | + public static Point2D convertPoint(Component source, Point2D aPoint, Component destination) { | |
45 | + return SwingUtilities.convertPoint(source, (int)aPoint.getX(), (int)aPoint.getY(), destination); | |
46 | + } | |
47 | +} |
@@ -25,7 +25,10 @@ | ||
25 | 25 | |
26 | 26 | package com.hironytic.moltonf.view; |
27 | 27 | |
28 | +import java.awt.AWTEvent; | |
29 | +import java.awt.AlphaComposite; | |
28 | 30 | import java.awt.Color; |
31 | +import java.awt.Composite; | |
29 | 32 | import java.awt.Cursor; |
30 | 33 | import java.awt.Dimension; |
31 | 34 | import java.awt.FontMetrics; |
@@ -32,9 +35,8 @@ | ||
32 | 35 | import java.awt.Graphics; |
33 | 36 | import java.awt.Graphics2D; |
34 | 37 | import java.awt.Paint; |
35 | -import java.awt.event.MouseAdapter; | |
38 | +import java.awt.Shape; | |
36 | 39 | import java.awt.event.MouseEvent; |
37 | -import java.awt.event.MouseMotionAdapter; | |
38 | 40 | import java.awt.font.FontRenderContext; |
39 | 41 | import java.awt.font.LineBreakMeasurer; |
40 | 42 | import java.awt.font.TextAttribute; |
@@ -41,6 +43,7 @@ | ||
41 | 43 | import java.awt.font.TextHitInfo; |
42 | 44 | import java.awt.font.TextLayout; |
43 | 45 | import java.awt.geom.Dimension2D; |
46 | +import java.awt.geom.Point2D; | |
44 | 47 | import java.awt.geom.Rectangle2D; |
45 | 48 | import java.text.AttributedCharacterIterator; |
46 | 49 | import java.text.AttributedString; |
@@ -65,7 +68,7 @@ | ||
65 | 68 | * ストーリー中のテキストを表示するコンポーネント |
66 | 69 | */ |
67 | 70 | @SuppressWarnings("serial") |
68 | -public class MessageComponent extends JComponent { | |
71 | +public class MessageComponent extends JComponent implements Selectable { | |
69 | 72 | |
70 | 73 | /** 表示するメッセージ */ |
71 | 74 | private List<String> messageLines; |
@@ -94,6 +97,15 @@ | ||
94 | 97 | /** イベント通知を受け取るリスナーのリスト */ |
95 | 98 | private final EventListenerList eventListenerList = new EventListenerList(); |
96 | 99 | |
100 | + /** 範囲選択オブジェクト */ | |
101 | + private RangeSelector rangeSelector = null; | |
102 | + | |
103 | + /** 選択範囲の開始点 */ | |
104 | + private Position startOfSelectedRange = null; | |
105 | + | |
106 | + /** 選択範囲の終了点 */ | |
107 | + private Position endOfSelectedRange = null; | |
108 | + | |
97 | 109 | /** |
98 | 110 | * テキストの属性が設定された箇所の情報 |
99 | 111 | */ |
@@ -300,14 +312,8 @@ | ||
300 | 312 | */ |
301 | 313 | public MessageComponent() { |
302 | 314 | lineHeightFactor = 1.3f; /* TODO: G国なら1.5f */ |
303 | - addMouseListener(new MouseAdapter() { | |
304 | - }); | |
305 | - addMouseMotionListener(new MouseMotionAdapter() { | |
306 | - @Override | |
307 | - public void mouseMoved(MouseEvent e) { | |
308 | - MessageComponent.this.mouseMoved(e); | |
309 | - } | |
310 | - }); | |
315 | + | |
316 | + enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); | |
311 | 317 | } |
312 | 318 | |
313 | 319 | /** |
@@ -382,6 +388,109 @@ | ||
382 | 388 | eventListenerList.remove(LinkClickListener.class, listener); |
383 | 389 | } |
384 | 390 | |
391 | + /** | |
392 | + * 範囲選択オブジェクトをセットします。 | |
393 | + * @param rangeSelector 範囲選択オブジェクト | |
394 | + */ | |
395 | + public void setRangeSelector(RangeSelector rangeSelector) { | |
396 | + this.rangeSelector = rangeSelector; | |
397 | + } | |
398 | + | |
399 | + /** | |
400 | + * メッセージ中の特定の位置を指すためのクラス | |
401 | + */ | |
402 | + private static class Position implements Comparable<Position> { | |
403 | + /** 見た目の行。textLayouts のインデックスと等しくなります。*/ | |
404 | + private int lineLayoutIndex; | |
405 | + | |
406 | + /** lineLayoutIndex で示される見た目の行内の、位置情報 */ | |
407 | + private TextHitInfo textHitInfo; | |
408 | + | |
409 | + /** | |
410 | + * コンストラクタ | |
411 | + * @param lineLayoutIndex 見た目の行。 | |
412 | + * @param textHitInfolineLayoutIndex で示される見た目の行内の、位置情報。 | |
413 | + */ | |
414 | + public Position(int lineLayoutIndex, TextHitInfo textHitInfo) { | |
415 | + this.lineLayoutIndex = lineLayoutIndex; | |
416 | + this.textHitInfo = textHitInfo; | |
417 | + } | |
418 | + | |
419 | + /** | |
420 | + * 見た目の行を取得します。 | |
421 | + * @return ヒットした見た目の行。textLayouts のインデックスと等しくなります。 | |
422 | + */ | |
423 | + public int getLineLayoutIndex() { | |
424 | + return lineLayoutIndex; | |
425 | + } | |
426 | + | |
427 | + /** | |
428 | + * getLineLayoutIndex() が返す見た目の行内の、位置情報を取得します。 | |
429 | + * @return 位置情報を返します。 | |
430 | + */ | |
431 | + public TextHitInfo getTextHitInfo() { | |
432 | + return textHitInfo; | |
433 | + } | |
434 | + | |
435 | + /** | |
436 | + * @see java.lang.Comparable#compareTo(java.lang.Object) | |
437 | + */ | |
438 | + @Override | |
439 | + public int compareTo(Position other) { | |
440 | + if (lineLayoutIndex < other.lineLayoutIndex) { | |
441 | + return -1; | |
442 | + } else if (lineLayoutIndex > other.lineLayoutIndex) { | |
443 | + return 1; | |
444 | + } else { | |
445 | + return (textHitInfo.getInsertionIndex() - other.textHitInfo.getInsertionIndex()); | |
446 | + } | |
447 | + } | |
448 | + | |
449 | + /** | |
450 | + * @see java.lang.Object#equals(java.lang.Object) | |
451 | + */ | |
452 | + @Override | |
453 | + public boolean equals(Object obj) { | |
454 | + if (this == obj) | |
455 | + return true; | |
456 | + if (obj == null) | |
457 | + return false; | |
458 | + if (getClass() != obj.getClass()) | |
459 | + return false; | |
460 | + Position other = (Position) obj; | |
461 | + if (lineLayoutIndex != other.lineLayoutIndex) | |
462 | + return false; | |
463 | + if (textHitInfo == null) { | |
464 | + if (other.textHitInfo != null) | |
465 | + return false; | |
466 | + } else if (!textHitInfo.equals(other.textHitInfo)) | |
467 | + return false; | |
468 | + return true; | |
469 | + } | |
470 | + | |
471 | + /** | |
472 | + * @see java.lang.Object#hashCode() | |
473 | + */ | |
474 | + @Override | |
475 | + public int hashCode() { | |
476 | + final int prime = 31; | |
477 | + int result = 1; | |
478 | + result = prime * result + lineLayoutIndex; | |
479 | + result = prime * result | |
480 | + + ((textHitInfo == null) ? 0 : textHitInfo.hashCode()); | |
481 | + return result; | |
482 | + } | |
483 | + | |
484 | + /** | |
485 | + * @see java.lang.Object#toString() | |
486 | + */ | |
487 | + @Override | |
488 | + public String toString() { | |
489 | + return "Position [lineLayoutIndex=" + lineLayoutIndex | |
490 | + + ", textHitInfo=" + textHitInfo + "]"; | |
491 | + } | |
492 | + } | |
493 | + | |
385 | 494 | /** ヒットした位置 */ |
386 | 495 | enum HitArea { |
387 | 496 | /** 範囲外 */ |
@@ -399,12 +508,9 @@ | ||
399 | 508 | /** ヒットした位置にあるもの */ |
400 | 509 | private HitArea hitArea = HitArea.NONE; |
401 | 510 | |
402 | - /** ヒットした見た目の行。textLayouts のインデックスと等しくなります。*/ | |
403 | - private int lineLayoutIndex = -1; | |
511 | + /** ヒットした位置 */ | |
512 | + private Position hitPosition = null; | |
404 | 513 | |
405 | - /** ヒットした見た目の行内の、ヒットした部分の情報 */ | |
406 | - private TextHitInfo textHitInfo = null; | |
407 | - | |
408 | 514 | /** ヒットしたリンクの情報 */ |
409 | 515 | private LinkInfo hitLinkInfo = null; |
410 | 516 |
@@ -417,22 +523,14 @@ | ||
417 | 523 | } |
418 | 524 | |
419 | 525 | /** |
420 | - * lineLayoutIndex を取得します。 | |
421 | - * @return lineLayoutIndex を返します。 | |
526 | + * hitPosition を取得します。 | |
527 | + * @return hitPosition を返します。 | |
422 | 528 | */ |
423 | - public int getLineLayoutIndex() { | |
424 | - return lineLayoutIndex; | |
529 | + public Position getHitPosition() { | |
530 | + return hitPosition; | |
425 | 531 | } |
426 | 532 | |
427 | 533 | /** |
428 | - * textHitInfo を取得します。 | |
429 | - * @return textHitInfo を返します。 | |
430 | - */ | |
431 | - public TextHitInfo getTextHitInfo() { | |
432 | - return textHitInfo; | |
433 | - } | |
434 | - | |
435 | - /** | |
436 | 534 | * hitLinkInfo を取得します。 |
437 | 535 | * @return hitLinkInfo を返します。 |
438 | 536 | */ |
@@ -449,22 +547,14 @@ | ||
449 | 547 | } |
450 | 548 | |
451 | 549 | /** |
452 | - * lineLayoutIndex をセットします。 | |
453 | - * @param lineLayoutIndex セットしたい lineLayoutIndex の値 | |
550 | + * hitPosition をセットします。 | |
551 | + * @param hitPosition セットしたい hitPosition の値 | |
454 | 552 | */ |
455 | - public void setLineLayoutIndex(int lineLayoutIndex) { | |
456 | - this.lineLayoutIndex = lineLayoutIndex; | |
553 | + public void setHitPosition(Position hitPosition) { | |
554 | + this.hitPosition = hitPosition; | |
457 | 555 | } |
458 | 556 | |
459 | 557 | /** |
460 | - * textHitInfo をセットします。 | |
461 | - * @param textHitInfo セットしたい textHitInfo の値 | |
462 | - */ | |
463 | - public void setTextHitInfo(TextHitInfo textHitInfo) { | |
464 | - this.textHitInfo = textHitInfo; | |
465 | - } | |
466 | - | |
467 | - /** | |
468 | 558 | * hitLinkInfo をセットします。 |
469 | 559 | * @param hitLinkInfo セットしたい hitLinkInfo の値 |
470 | 560 | */ |
@@ -483,15 +573,17 @@ | ||
483 | 573 | private HitResult hitTest(float x, float y) { |
484 | 574 | HitResult result = new HitResult(); |
485 | 575 | |
486 | - if (x < 0 || x > areaSize.width || y < 0 || y > areaSize.height) { | |
576 | + if (lineLayouts == null || | |
577 | + x < 0 || x > areaSize.width || y < 0 || y > areaSize.height) { | |
487 | 578 | // エリア外 |
488 | 579 | result.setHitArea(HitArea.NONE); |
489 | 580 | } else { |
490 | 581 | LineLayout lineLayout = null; |
582 | + int lineLayoutIndex = -1; | |
491 | 583 | for (int ix = lineLayouts.size() - 1; ix >= 0; --ix) { |
492 | 584 | lineLayout = lineLayouts.get(ix); |
493 | 585 | if (lineLayout.getTop() < y) { |
494 | - result.setLineLayoutIndex(ix); | |
586 | + lineLayoutIndex = ix; | |
495 | 587 | break; |
496 | 588 | } |
497 | 589 | } |
@@ -506,10 +598,10 @@ | ||
506 | 598 | if (textLayout == null) { |
507 | 599 | // 空行の場合 |
508 | 600 | result.setHitArea(HitArea.NONE); |
509 | - result.setTextHitInfo(TextHitInfo.beforeOffset(0)); | |
601 | + result.setHitPosition(new Position(lineLayoutIndex, TextHitInfo.leading(0))); | |
510 | 602 | } else { |
511 | 603 | TextHitInfo textHitInfo = textLayout.hitTestChar(x, y); |
512 | - result.setTextHitInfo(textHitInfo); | |
604 | + result.setHitPosition(new Position(lineLayoutIndex, textHitInfo)); | |
513 | 605 | int lineIndex = lineLayout.getLineIndex(); |
514 | 606 | int charIndex = lineLayout.getStartCharIndex() + textHitInfo.getCharIndex(); |
515 | 607 |
@@ -549,11 +641,75 @@ | ||
549 | 641 | } |
550 | 642 | |
551 | 643 | /** |
552 | - * マウスカーソルが動いたときにリスナーから呼ばれます。 | |
553 | - * @param e マウスイベント | |
644 | + * マウスイベントを処理している間の情報 | |
554 | 645 | */ |
555 | - private void mouseMoved(MouseEvent e) { | |
556 | - HitResult hitResult = hitTest(e.getX(), e.getY()); | |
646 | + private static class MouseEventProcessingContext { | |
647 | + /** 対象のマウスイベント */ | |
648 | + private MouseEvent event; | |
649 | + | |
650 | + /** 範囲選択の処理を行うかどうか */ | |
651 | + private boolean isProcessRangeSelection = true; | |
652 | + | |
653 | + public MouseEventProcessingContext(MouseEvent event) { | |
654 | + this.event = event; | |
655 | + } | |
656 | + | |
657 | + public MouseEvent getEvent() { | |
658 | + return event; | |
659 | + } | |
660 | + | |
661 | + public boolean isProcessRangeSelection() { | |
662 | + return isProcessRangeSelection; | |
663 | + } | |
664 | + | |
665 | + public void setProcessRangeSelection(boolean isProcessRangeSelection) { | |
666 | + this.isProcessRangeSelection = isProcessRangeSelection; | |
667 | + } | |
668 | + } | |
669 | + | |
670 | + /** | |
671 | + * @see javax.swing.JComponent#processMouseEvent(java.awt.event.MouseEvent) | |
672 | + */ | |
673 | + @Override | |
674 | + protected void processMouseEvent(MouseEvent e) { | |
675 | + processMouseRelatedEvent(e); | |
676 | + super.processMouseEvent(e); | |
677 | + } | |
678 | + | |
679 | + /** | |
680 | + * @see javax.swing.JComponent#processMouseMotionEvent(java.awt.event.MouseEvent) | |
681 | + */ | |
682 | + @Override | |
683 | + protected void processMouseMotionEvent(MouseEvent e) { | |
684 | + processMouseRelatedEvent(e); | |
685 | + super.processMouseMotionEvent(e); | |
686 | + } | |
687 | + | |
688 | + /** | |
689 | + * マウスに関連するイベントを処理します。 | |
690 | + * @param e | |
691 | + */ | |
692 | + private void processMouseRelatedEvent(MouseEvent e) { | |
693 | + MouseEventProcessingContext ctx = new MouseEventProcessingContext(e); | |
694 | + int eventId = e.getID(); | |
695 | + if (eventId == MouseEvent.MOUSE_MOVED) { | |
696 | + mouseMoved(ctx); | |
697 | + } else if (eventId == MouseEvent.MOUSE_CLICKED) { | |
698 | + mouseClicked(ctx); | |
699 | + } | |
700 | + | |
701 | + if (ctx.isProcessRangeSelection() && rangeSelector != null) { | |
702 | + rangeSelector.processMouseRelatedEvent(e); | |
703 | + } | |
704 | + } | |
705 | + | |
706 | + /** | |
707 | + * マウスカーソルが動いたときの処理 | |
708 | + * @param ctx 処理コンテキスト | |
709 | + */ | |
710 | + private void mouseMoved(MouseEventProcessingContext ctx) { | |
711 | + MouseEvent event = ctx.getEvent(); | |
712 | + HitResult hitResult = hitTest(event.getX(), event.getY()); | |
557 | 713 | HitArea hitArea = hitResult.getHitArea(); |
558 | 714 | if (hitArea == HitArea.LINK) { |
559 | 715 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
@@ -565,10 +721,27 @@ | ||
565 | 721 | } |
566 | 722 | |
567 | 723 | /** |
724 | + * マウスがクリックされたときにリスナーから呼ばれます。 | |
725 | + * @param e マウスイベント | |
726 | + */ | |
727 | + private void mouseClicked(MouseEventProcessingContext ctx) { | |
728 | + MouseEvent event = ctx.getEvent(); | |
729 | + HitResult hitResult = hitTest(event.getX(), event.getY()); | |
730 | + HitArea hitArea = hitResult.getHitArea(); | |
731 | + | |
732 | + // リンクのクリックか? | |
733 | + if (hitArea == HitArea.LINK){ | |
734 | + fireLinkClicked(hitResult.getHitLinkInfo().getLink(), event); | |
735 | + ctx.setProcessRangeSelection(false); | |
736 | + } | |
737 | + } | |
738 | + | |
739 | + /** | |
568 | 740 | * リンクがクリックされたことを通知します。 |
569 | 741 | * @param link クリックされたリンク |
742 | + * @param mouseEvent 元となったマウスイベント | |
570 | 743 | */ |
571 | - private void fireLinkClicked(Link link) { | |
744 | + private void fireLinkClicked(Link link, MouseEvent mouseEvent) { | |
572 | 745 | // Guaranteed to return a non-null array |
573 | 746 | Object[] listeners = eventListenerList.getListenerList(); |
574 | 747 | // Process the listeners last to first, notifying |
@@ -738,9 +911,44 @@ | ||
738 | 911 | |
739 | 912 | Graphics2D g2 = (Graphics2D)g; |
740 | 913 | if (lineLayouts != null) { |
741 | - for (LineLayout lineLayout : lineLayouts) { | |
914 | + for (int lineLayoutIndex = 0; lineLayoutIndex < lineLayouts.size(); ++lineLayoutIndex) { | |
915 | + LineLayout lineLayout = lineLayouts.get(lineLayoutIndex); | |
742 | 916 | TextLayout textLayout = lineLayout.getTextLayout(); |
743 | 917 | if (textLayout != null) { |
918 | + // 選択範囲の描画 | |
919 | + if (startOfSelectedRange != null && endOfSelectedRange != null) { | |
920 | + if (startOfSelectedRange.getLineLayoutIndex() <= lineLayoutIndex && | |
921 | + endOfSelectedRange.getLineLayoutIndex() >= lineLayoutIndex) { | |
922 | + TextHitInfo beginInfo; | |
923 | + if (startOfSelectedRange.getLineLayoutIndex() == lineLayoutIndex) { | |
924 | + beginInfo = startOfSelectedRange.getTextHitInfo(); | |
925 | + } else { | |
926 | + beginInfo = TextHitInfo.leading(0); | |
927 | + } | |
928 | + TextHitInfo endInfo; | |
929 | + if (endOfSelectedRange.getLineLayoutIndex() == lineLayoutIndex) { | |
930 | + endInfo = endOfSelectedRange.getTextHitInfo(); | |
931 | + } else { | |
932 | + endInfo = TextHitInfo.trailing(textLayout.getCharacterCount() - 1); | |
933 | + } | |
934 | + Shape selectionShape = textLayout.getVisualHighlightShape(beginInfo, endInfo); | |
935 | + Color oldColor = g2.getColor(); | |
936 | + Composite oldComposite = g2.getComposite(); | |
937 | + g2.setPaintMode(); | |
938 | + g2.setColor(new Color(0x55BBff)); // TODO: 選択範囲の色。どこで定義する? | |
939 | + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); // TODO: 選択範囲のアルファ値。どこで定義する? | |
940 | + float tx = messageAreaRect.x; | |
941 | + float ty = lineLayout.getTop() + textLayout.getAscent(); | |
942 | + g2.translate(tx, ty); | |
943 | + g2.fill(selectionShape); | |
944 | + g2.translate(-tx, -ty); | |
945 | + g2.setComposite(oldComposite); | |
946 | + g2.setColor(oldColor); | |
947 | + } | |
948 | + } | |
949 | + | |
950 | + // 文字列の描画 | |
951 | + g2.setPaintMode(); | |
744 | 952 | textLayout.draw(g2, messageAreaRect.x, |
745 | 953 | lineLayout.getTop() + textLayout.getAscent()); |
746 | 954 | } |
@@ -747,4 +955,94 @@ | ||
747 | 955 | } |
748 | 956 | } |
749 | 957 | } |
958 | + | |
959 | + /** | |
960 | + * @see com.hironytic.moltonf.view.Selectable#clearSelection() | |
961 | + */ | |
962 | + @Override | |
963 | + public void clearSelection() { | |
964 | + if (startOfSelectedRange != null || endOfSelectedRange != null) { | |
965 | + startOfSelectedRange = null; | |
966 | + endOfSelectedRange = null; | |
967 | + repaint(); | |
968 | + } | |
969 | + } | |
970 | + | |
971 | + /** | |
972 | + * @see com.hironytic.moltonf.view.Selectable#selectRange(java.awt.geom.Point2D, java.awt.geom.Point2D) | |
973 | + */ | |
974 | + @Override | |
975 | + public void selectRange(Point2D startPt, Point2D endPt) { | |
976 | + if (lineLayouts == null) { | |
977 | + return; | |
978 | + } | |
979 | + | |
980 | + // startPt と endPt を Y座標の小さい順にする | |
981 | + if (startPt.getY() > endPt.getY()){ | |
982 | + Point2D tempPt = endPt; | |
983 | + endPt = startPt; | |
984 | + startPt = tempPt; | |
985 | + } | |
986 | + float startX = (float)startPt.getX(); | |
987 | + float startY = (float)startPt.getY(); | |
988 | + float endX = (float)endPt.getX(); | |
989 | + float endY = (float)endPt.getY(); | |
990 | + | |
991 | + // X座標について、範囲をはみ出したものを範囲内へ持ってくる | |
992 | + if (startX < 0) { | |
993 | + startX = 0; | |
994 | + } else if (startX > areaSize.width) { | |
995 | + startX = (int)areaSize.width; | |
996 | + } | |
997 | + if (endX < 0) { | |
998 | + endX = 0; | |
999 | + } else if (endX > areaSize.width) { | |
1000 | + endX = (int)areaSize.width; | |
1001 | + } | |
1002 | + | |
1003 | + Position startPos = null; | |
1004 | + if (startY < 0) { | |
1005 | + // 先頭から選択 | |
1006 | + startPos = new Position(0, TextHitInfo.leading(0)); | |
1007 | + } else if (startY > areaSize.height) { | |
1008 | + startPos = null; // 選択範囲なし | |
1009 | + } else { | |
1010 | + startPos = hitTest(startX, startY).getHitPosition(); | |
1011 | + } | |
1012 | + | |
1013 | + Position endPos = null; | |
1014 | + if (endY < 0) { | |
1015 | + startPos = null; // 選択範囲なし | |
1016 | + } else if (endY > areaSize.height){ | |
1017 | + // 末尾まで選択 | |
1018 | + int lineLayoutIndex = lineLayouts.size() - 1; | |
1019 | + TextLayout textLayout = lineLayouts.get(lineLayoutIndex).getTextLayout(); | |
1020 | + TextHitInfo textHitInfo; | |
1021 | + if (textLayout == null) { // 最後が空行はあり得ないはずだが念のため | |
1022 | + textHitInfo = TextHitInfo.leading(0); | |
1023 | + } else { | |
1024 | + textHitInfo = TextHitInfo.trailing(textLayout.getCharacterCount() - 1); | |
1025 | + } | |
1026 | + endPos = new Position(lineLayoutIndex, textHitInfo); | |
1027 | + } else { | |
1028 | + endPos = hitTest(endX, endY).getHitPosition(); | |
1029 | + } | |
1030 | + | |
1031 | + if (startPos == null || endPos == null) { | |
1032 | + clearSelection(); | |
1033 | + } else { | |
1034 | + // 開始位置が終了位置より後にあれば入れ替える | |
1035 | + if (startPos.compareTo(endPos) > 0) { | |
1036 | + Position tempPos = endPos; | |
1037 | + endPos = startPos; | |
1038 | + startPos = tempPos; | |
1039 | + } | |
1040 | + | |
1041 | + if (!startPos.equals(startOfSelectedRange) || !endPos.equals(endOfSelectedRange)) { | |
1042 | + startOfSelectedRange = startPos; | |
1043 | + endOfSelectedRange = endPos; | |
1044 | + repaint(); | |
1045 | + } | |
1046 | + } | |
1047 | + } | |
750 | 1048 | } |
@@ -25,6 +25,7 @@ | ||
25 | 25 | |
26 | 26 | package com.hironytic.moltonf.view; |
27 | 27 | |
28 | +import java.awt.AWTEvent; | |
28 | 29 | import java.awt.Color; |
29 | 30 | import java.awt.Component; |
30 | 31 | import java.awt.FlowLayout; |
@@ -32,7 +33,10 @@ | ||
32 | 33 | import java.awt.Graphics; |
33 | 34 | import java.awt.Graphics2D; |
34 | 35 | import java.awt.Point; |
36 | +import java.awt.Rectangle; | |
37 | +import java.awt.event.MouseEvent; | |
35 | 38 | import java.awt.geom.Dimension2D; |
39 | +import java.awt.geom.Point2D; | |
36 | 40 | import java.awt.geom.Rectangle2D; |
37 | 41 | import java.util.Arrays; |
38 | 42 | import java.util.List; |
@@ -99,6 +103,9 @@ | ||
99 | 103 | |
100 | 104 | /** 強調表示設定 */ |
101 | 105 | private List<HighlightSetting> highlightSettingList; |
106 | + | |
107 | + /** 範囲選択用オブジェクト */ | |
108 | + private RangeSelector rangeSelector = new PeriodViewRangeSelector(); | |
102 | 109 | |
103 | 110 | private MessageComponent nextDayLink; // TODO: MoltonfView できちんと実装すれば持つ必要ない |
104 | 111 |
@@ -129,6 +136,8 @@ | ||
129 | 136 | contentPanel.add(this); |
130 | 137 | scrollPane = new ScrollPane(this); |
131 | 138 | scrollPane.setViewportView(contentPanel); |
139 | + | |
140 | + enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); | |
132 | 141 | } |
133 | 142 | |
134 | 143 | /** |
@@ -356,6 +365,7 @@ | ||
356 | 365 | talkView.setAreaWidth(500); //TODO: |
357 | 366 | talkView.setHighlightSettingList(highlightSettingList); |
358 | 367 | talkView.setFont(getFont()); |
368 | + talkView.setRangeSelector(rangeSelector); | |
359 | 369 | TalkType talkType = talk.getTalkType(); |
360 | 370 | Avatar speaker = talk.getSpeaker(); |
361 | 371 | if (isMatchFilterOfTalk(talkType) && isMatchFilterOfSpeaker(speaker)) { |
@@ -369,6 +379,7 @@ | ||
369 | 379 | storyEventView.setStoryEvent(storyEvent); |
370 | 380 | storyEventView.setAreaWidth(500); // TODO: |
371 | 381 | storyEventView.setFont(getFont()); |
382 | + storyEventView.setRangeSelector(rangeSelector); | |
372 | 383 | EventFamily eventFamily = storyEvent.getEventFamily(); |
373 | 384 | if (isMatchFilterOfEvent(eventFamily)) { |
374 | 385 | isVisible = true; |
@@ -399,24 +410,6 @@ | ||
399 | 410 | add(nextDayLink); |
400 | 411 | } |
401 | 412 | |
402 | -// /** | |
403 | -// * 指定された位置にある可視の子コンポーネントを返します。 | |
404 | -// * @param x X座標 | |
405 | -// * @param y Y座標 | |
406 | -// * @return 表示されている子コンポーネント。 | |
407 | -// * その位置に可視の子コンポーネントが見つからなければ null を返します。 | |
408 | -// */ | |
409 | -// private Component getVisibleChildComponentAt(int x, int y) { | |
410 | -// int componentCount = getComponentCount(); | |
411 | -// for (int ix = 0; ix < componentCount; ++ix) { | |
412 | -// Component comp = getComponent(ix); | |
413 | -// if (comp.isVisible() && comp.contains(x - comp.getX(), y - comp.getY())) { | |
414 | -// return comp; | |
415 | -// } | |
416 | -// } | |
417 | -// return null; | |
418 | -// } | |
419 | - | |
420 | 413 | /** |
421 | 414 | * フィルタリング状態を再構成します。 |
422 | 415 | */ |
@@ -549,4 +542,168 @@ | ||
549 | 542 | g2d.fill(paintRect); |
550 | 543 | g2d.setColor(oldColor); |
551 | 544 | } |
545 | + | |
546 | + /** | |
547 | + * PeriodView 用の範囲選択用オブジェクトの実装 | |
548 | + */ | |
549 | + private class PeriodViewRangeSelector implements RangeSelector { | |
550 | + /** 開始点の子コンポーネントのインデックス */ | |
551 | + private int startChildIndex = -1; | |
552 | + | |
553 | + /** 開始点の座標。PeriodView の座標系です。*/ | |
554 | + private Point2D startPt = null; | |
555 | + | |
556 | + /** 終了点の子コンポーネントのインデックス */ | |
557 | + private int endChildIndex = -1; | |
558 | + | |
559 | + /** 終了点の座標。PeriodView の座標系です。*/ | |
560 | + private Point2D endPt = null; | |
561 | + | |
562 | + public PeriodViewRangeSelector() { | |
563 | + } | |
564 | + | |
565 | + /** | |
566 | + * @see com.hironytic.moltonf.view.RangeSelector#processMouseRelatedEvent(java.awt.event.MouseEvent) | |
567 | + */ | |
568 | + @Override | |
569 | + public void processMouseRelatedEvent(MouseEvent event) { | |
570 | + int eventId = event.getID(); | |
571 | + if (eventId == MouseEvent.MOUSE_PRESSED) { | |
572 | + if (SwingUtilities.isLeftMouseButton(event) || SwingUtilities.isRightMouseButton(event)) { | |
573 | + if (event.isShiftDown()) { | |
574 | + selectTo((JComponent)event.getSource(), event.getPoint()); | |
575 | + } else { | |
576 | + selectFrom((JComponent)event.getSource(), event.getPoint()); | |
577 | + } | |
578 | + } | |
579 | + } else if (eventId == MouseEvent.MOUSE_DRAGGED) { | |
580 | + if (SwingUtilities.isLeftMouseButton(event) || SwingUtilities.isRightMouseButton(event)) { | |
581 | + selectTo((JComponent)event.getSource(), event.getPoint()); | |
582 | + | |
583 | + // TODO: ちょっと雑すぎる? | |
584 | + Point2D point = ViewUtilities.convertPoint((JComponent)event.getSource(), event.getPoint(), PeriodView.this); | |
585 | + Rectangle rect = new Rectangle((int)point.getX(), (int)point.getY(), 1, 1); | |
586 | + scrollRectToVisible(rect); | |
587 | + } | |
588 | + } | |
589 | + } | |
590 | + | |
591 | + /** | |
592 | + * 指定された位置にある子コンポーネントのインデックスを返します。 | |
593 | + * @param point 位置 | |
594 | + * @return 子コンポーネントのインデックス。 | |
595 | + * その位置に可視の子コンポーネントが見つからなければ -1 を返します。 | |
596 | + */ | |
597 | + private int getChildComponent(Point2D point) { | |
598 | + // コンポーネントは上から順に並んでいるという前提 | |
599 | + int lastItemIndex = -1; | |
600 | + int componentCount = getComponentCount(); | |
601 | + for (int ix = 0; ix < componentCount; ++ix) { | |
602 | + Component comp = getComponent(ix); | |
603 | + if (comp.isVisible()) { | |
604 | + lastItemIndex = ix; | |
605 | + if (comp.getY() + comp.getHeight() >= point.getY()) { | |
606 | + return ix; | |
607 | + } | |
608 | + } | |
609 | + } | |
610 | + | |
611 | + // 見つからなければ最後のアイテムで | |
612 | + return lastItemIndex; | |
613 | + } | |
614 | + | |
615 | + /** | |
616 | + * 選択範囲を消去します。 | |
617 | + */ | |
618 | + private void doClearSelection() { | |
619 | + if (startChildIndex < 0 || endChildIndex < 0) { | |
620 | + return; | |
621 | + } | |
622 | + | |
623 | + int startIx = (startChildIndex < endChildIndex) ? startChildIndex : endChildIndex; | |
624 | + int endIx = (startChildIndex < endChildIndex) ? endChildIndex : startChildIndex; | |
625 | + for (int ix = startIx; ix <= endIx; ++ix) { | |
626 | + Component child = getComponent(ix); | |
627 | + if (child instanceof Selectable) { | |
628 | + Selectable selectableChild = (Selectable)child; | |
629 | + selectableChild.clearSelection(); | |
630 | + } | |
631 | + } | |
632 | + startPt = null; | |
633 | + startChildIndex = -1; | |
634 | + } | |
635 | + | |
636 | + /** | |
637 | + * 指定したコンポーネントのある点を選択範囲の開始点にします。 | |
638 | + * @param component コンポーネント | |
639 | + * @param pt 開始点。第1引数で指定したコンポーネントの座標系で指定します。 | |
640 | + */ | |
641 | + private void selectFrom(JComponent component, Point2D pt) { | |
642 | + doClearSelection(); | |
643 | + | |
644 | + startPt = ViewUtilities.convertPoint(component, pt, PeriodView.this); | |
645 | + startChildIndex = getChildComponent(startPt); | |
646 | + endPt = null; | |
647 | + endChildIndex = -1; | |
648 | + } | |
649 | + | |
650 | + /** | |
651 | + * 指定したコンポーネントのある点を選択範囲の終了点にします。 | |
652 | + * @param component コンポーネント | |
653 | + * @param pt 終了点。第1引数で指定したコンポーネントの座標系で指定します。 | |
654 | + */ | |
655 | + private void selectTo(JComponent component, Point2D pt) { | |
656 | + if (startPt == null) { | |
657 | + return; | |
658 | + } | |
659 | + | |
660 | + int prevEndChildIndex = endChildIndex; | |
661 | + | |
662 | + endPt = ViewUtilities.convertPoint(component, pt, PeriodView.this); | |
663 | + endChildIndex = getChildComponent(endPt); | |
664 | + | |
665 | + int startIx = (-1 == prevEndChildIndex) ? startChildIndex : prevEndChildIndex; | |
666 | + int endIx = endChildIndex; | |
667 | + if (startIx > endIx) { | |
668 | + int tempIx = endIx; | |
669 | + endIx = startIx; | |
670 | + startIx = tempIx; | |
671 | + } | |
672 | + for (int ix = startIx; ix <= endIx; ++ix) { | |
673 | + Component child = getComponent(ix); | |
674 | + if (child instanceof Selectable) { | |
675 | + Selectable selectableChild = (Selectable)child; | |
676 | + Point2D childStart = ViewUtilities.convertPoint(PeriodView.this, startPt, child); | |
677 | + Point2D childEnd = ViewUtilities.convertPoint(PeriodView.this, endPt, child); | |
678 | + selectableChild.selectRange(childStart, childEnd); | |
679 | + } | |
680 | + } | |
681 | + } | |
682 | + } | |
683 | + | |
684 | + /** | |
685 | + * @see javax.swing.JComponent#processMouseEvent(java.awt.event.MouseEvent) | |
686 | + */ | |
687 | + @Override | |
688 | + protected void processMouseEvent(MouseEvent e) { | |
689 | + processMouseRelatedEvent(e); | |
690 | + super.processMouseEvent(e); | |
691 | + } | |
692 | + | |
693 | + /** | |
694 | + * @see javax.swing.JComponent#processMouseMotionEvent(java.awt.event.MouseEvent) | |
695 | + */ | |
696 | + @Override | |
697 | + protected void processMouseMotionEvent(MouseEvent e) { | |
698 | + processMouseRelatedEvent(e); | |
699 | + super.processMouseMotionEvent(e); | |
700 | + } | |
701 | + | |
702 | + /** | |
703 | + * マウスに関連するイベントを処理します。 | |
704 | + * @param event マウス関連のイベント | |
705 | + */ | |
706 | + private void processMouseRelatedEvent(MouseEvent event) { | |
707 | + rangeSelector.processMouseRelatedEvent(event); | |
708 | + } | |
552 | 709 | } |
@@ -35,6 +35,7 @@ | ||
35 | 35 | import java.awt.RenderingHints; |
36 | 36 | import java.awt.Stroke; |
37 | 37 | import java.awt.geom.Dimension2D; |
38 | +import java.awt.geom.Point2D; | |
38 | 39 | import java.awt.geom.Rectangle2D; |
39 | 40 | |
40 | 41 | import javax.swing.JComponent; |
@@ -45,7 +46,7 @@ | ||
45 | 46 | * ストーリー中のイベントの表示を担当するビュー |
46 | 47 | */ |
47 | 48 | @SuppressWarnings("serial") |
48 | -public class StoryEventView extends JComponent implements MoltonfView { | |
49 | +public class StoryEventView extends JComponent implements MoltonfView, Selectable { | |
49 | 50 | |
50 | 51 | /** ビューの左の余白 */ |
51 | 52 | private static final float VIEW_PADDING_LEFT = 16; |
@@ -235,4 +236,45 @@ | ||
235 | 236 | |
236 | 237 | eventMessageComponent.setFont(font); |
237 | 238 | } |
239 | + | |
240 | + /** | |
241 | + * 範囲選択オブジェクトをセットします。 | |
242 | + * @param rangeSelector 範囲選択オブジェクト | |
243 | + */ | |
244 | + public void setRangeSelector(RangeSelector rangeSelector) { | |
245 | + eventMessageComponent.setRangeSelector(rangeSelector); | |
246 | + } | |
247 | + | |
248 | + /** | |
249 | + * @see com.hironytic.moltonf.view.Selectable#clearSelection() | |
250 | + */ | |
251 | + @Override | |
252 | + public void clearSelection() { | |
253 | + eventMessageComponent.clearSelection(); | |
254 | + } | |
255 | + | |
256 | + /** | |
257 | + * @see com.hironytic.moltonf.view.Selectable#selectRange(java.awt.geom.Point2D, java.awt.geom.Point2D) | |
258 | + */ | |
259 | + @Override | |
260 | + public void selectRange(Point2D startPt, Point2D endPt) { | |
261 | + if (startPt.getY() > endPt.getY()){ | |
262 | + Point2D tempPt = endPt; | |
263 | + endPt = startPt; | |
264 | + startPt = tempPt; | |
265 | + } | |
266 | + | |
267 | + Rectangle2D messageBounds = eventMessageComponent.getBounds(); | |
268 | + | |
269 | + // メッセージ領域外なら選択しない | |
270 | + if (startPt.getY() > messageBounds.getMaxY() || | |
271 | + endPt.getY() < messageBounds.getMinY()) { | |
272 | + clearSelection(); | |
273 | + return; | |
274 | + } | |
275 | + | |
276 | + Point2D eventMessageStartPt = ViewUtilities.convertPoint(this, startPt, eventMessageComponent); | |
277 | + Point2D eventMessageEndPt = ViewUtilities.convertPoint(this, endPt, eventMessageComponent); | |
278 | + eventMessageComponent.selectRange(eventMessageStartPt, eventMessageEndPt); | |
279 | + } | |
238 | 280 | } |
@@ -0,0 +1,40 @@ | ||
1 | +/* | |
2 | + * Moltonf | |
3 | + * | |
4 | + * Copyright (c) 2010 Project Moltonf | |
5 | + * | |
6 | + * Permission is hereby granted, free of charge, to any person obtaining | |
7 | + * a copy of this software and associated documentation files (the | |
8 | + * "Software"), to deal in the Software without restriction, including | |
9 | + * without limitation the rights to use, copy, modify, merge, publish, | |
10 | + * distribute, sublicense, and/or sell copies of the Software, and to | |
11 | + * permit persons to whom the Software is furnished to do so, subject to | |
12 | + * the following conditions: | |
13 | + * | |
14 | + * The above copyright notice and this permission notice shall be included | |
15 | + * in all copies or substantial portions of the Software. | |
16 | + * | |
17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
19 | + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
20 | + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
21 | + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
22 | + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
23 | + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
24 | + */ | |
25 | + | |
26 | +package com.hironytic.moltonf.view; | |
27 | + | |
28 | +import java.awt.event.MouseEvent; | |
29 | + | |
30 | +/** | |
31 | + * 範囲選択を司るオブジェクトが実装するインタフェース | |
32 | + */ | |
33 | +public interface RangeSelector { | |
34 | + | |
35 | + /** | |
36 | + * 範囲選択に関するマウスイベントを処理します。 | |
37 | + * @param e | |
38 | + */ | |
39 | + public void processMouseRelatedEvent(MouseEvent e); | |
40 | +} |