• R/O
  • SSH
  • HTTPS

moltonf: Commit


Commit MetaInfo

Revision5 (tree)
Time2010-07-06 22:24:11
Authorhironytic

Log Message

範囲選択の実装。
範囲選択はできても、選択範囲に対して何か処理を行うことはできない。
また、範囲選択中のオートスクロールの実装がかなり雑。要見直し。

Change Summary

Incremental Difference

--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/event/LinkClickedEvent.java (revision 4)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/event/LinkClickedEvent.java (revision 5)
@@ -25,6 +25,7 @@
2525
2626 package com.hironytic.moltonf.view.event;
2727
28+import java.awt.event.MouseEvent;
2829 import java.util.EventObject;
2930
3031 import com.hironytic.moltonf.model.Link;
@@ -38,6 +39,9 @@
3839 /** クリックされたリンク */
3940 private Link link;
4041
42+ /** クリックの引き金になったマウスイベント */
43+ private MouseEvent mouseEvent;
44+
4145 /**
4246 * コンストラクタ
4347 * @param source イベントが最初に発生したオブジェクト
@@ -63,6 +67,22 @@
6367 }
6468
6569 /**
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+ /**
6686 * @see java.lang.Object#toString()
6787 */
6888 @Override
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/Selectable.java (nonexistent)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/Selectable.java (revision 5)
@@ -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+}
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/TalkView.java (revision 4)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/TalkView.java (revision 5)
@@ -38,10 +38,10 @@
3838 import java.awt.geom.Dimension2D;
3939 import java.awt.geom.Ellipse2D;
4040 import java.awt.geom.Path2D;
41+import java.awt.geom.Point2D;
4142 import java.awt.geom.Rectangle2D;
4243 import java.awt.geom.RoundRectangle2D;
4344 import java.awt.image.ImageObserver;
44-import java.util.ArrayList;
4545 import java.util.Arrays;
4646 import java.util.Collections;
4747 import java.util.List;
@@ -49,7 +49,6 @@
4949 import javax.swing.JComponent;
5050
5151 import com.hironytic.moltonf.model.HighlightSetting;
52-import com.hironytic.moltonf.model.Link;
5352 import com.hironytic.moltonf.model.MessageRange;
5453 import com.hironytic.moltonf.model.Talk;
5554 import com.hironytic.moltonf.model.TalkType;
@@ -59,7 +58,7 @@
5958 * 発言の表示を担当するビュー
6059 */
6160 @SuppressWarnings("serial")
62-public class TalkView extends JComponent implements MoltonfView {
61+public class TalkView extends JComponent implements MoltonfView, Selectable {
6362
6463 /** ビューの左の余白 */
6564 private static final float VIEW_PADDING_LEFT = 16;
@@ -127,9 +126,6 @@
127126 /** 墓下メッセージの文字色 */
128127 private static final Color MESSAGE_TEXT_COLOR_GRAVE = new Color(0x000000);
129128
130- /** 通常のリンクの色 */
131- private static final Color LINK_COLOR = new Color(0xff8800);
132-
133129 /** 発言中のリンクの色 */
134130 private static final Color LINK_COLOR_IN_TALK = new Color(0xff0000);
135131
@@ -423,15 +419,8 @@
423419 // 発言
424420 talkMessageComponent.setMessage(talk.getMessageLines());
425421 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
434422 talkMessageComponent.updateLayout(areaSize.width - (VIEW_PADDING_LEFT + MESSAGE_LEFT + MESSAGE_PADDING_LEFT + MESSAGE_PADDING_RIGHT + VIEW_PADDING_RIGHT));
423+
435424 Dimension2D messageAreaSize = talkMessageComponent.getAreaSize();
436425 Rectangle2D messageAreaRect = new Rectangle2D.Float(
437426 VIEW_PADDING_LEFT + MESSAGE_LEFT + MESSAGE_PADDING_LEFT,
@@ -466,4 +455,55 @@
466455 talkInfoComponent.setFont(font);
467456 talkMessageComponent.setFont(font);
468457 }
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+ }
469509 }
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/ViewUtilities.java (nonexistent)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/ViewUtilities.java (revision 5)
@@ -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+}
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/MessageComponent.java (revision 4)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/MessageComponent.java (revision 5)
@@ -25,7 +25,10 @@
2525
2626 package com.hironytic.moltonf.view;
2727
28+import java.awt.AWTEvent;
29+import java.awt.AlphaComposite;
2830 import java.awt.Color;
31+import java.awt.Composite;
2932 import java.awt.Cursor;
3033 import java.awt.Dimension;
3134 import java.awt.FontMetrics;
@@ -32,9 +35,8 @@
3235 import java.awt.Graphics;
3336 import java.awt.Graphics2D;
3437 import java.awt.Paint;
35-import java.awt.event.MouseAdapter;
38+import java.awt.Shape;
3639 import java.awt.event.MouseEvent;
37-import java.awt.event.MouseMotionAdapter;
3840 import java.awt.font.FontRenderContext;
3941 import java.awt.font.LineBreakMeasurer;
4042 import java.awt.font.TextAttribute;
@@ -41,6 +43,7 @@
4143 import java.awt.font.TextHitInfo;
4244 import java.awt.font.TextLayout;
4345 import java.awt.geom.Dimension2D;
46+import java.awt.geom.Point2D;
4447 import java.awt.geom.Rectangle2D;
4548 import java.text.AttributedCharacterIterator;
4649 import java.text.AttributedString;
@@ -65,7 +68,7 @@
6568 * ストーリー中のテキストを表示するコンポーネント
6669 */
6770 @SuppressWarnings("serial")
68-public class MessageComponent extends JComponent {
71+public class MessageComponent extends JComponent implements Selectable {
6972
7073 /** 表示するメッセージ */
7174 private List<String> messageLines;
@@ -94,6 +97,15 @@
9497 /** イベント通知を受け取るリスナーのリスト */
9598 private final EventListenerList eventListenerList = new EventListenerList();
9699
100+ /** 範囲選択オブジェクト */
101+ private RangeSelector rangeSelector = null;
102+
103+ /** 選択範囲の開始点 */
104+ private Position startOfSelectedRange = null;
105+
106+ /** 選択範囲の終了点 */
107+ private Position endOfSelectedRange = null;
108+
97109 /**
98110 * テキストの属性が設定された箇所の情報
99111 */
@@ -300,14 +312,8 @@
300312 */
301313 public MessageComponent() {
302314 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);
311317 }
312318
313319 /**
@@ -382,6 +388,109 @@
382388 eventListenerList.remove(LinkClickListener.class, listener);
383389 }
384390
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+
385494 /** ヒットした位置 */
386495 enum HitArea {
387496 /** 範囲外 */
@@ -399,12 +508,9 @@
399508 /** ヒットした位置にあるもの */
400509 private HitArea hitArea = HitArea.NONE;
401510
402- /** ヒットした見た目の行。textLayouts のインデックスと等しくなります。*/
403- private int lineLayoutIndex = -1;
511+ /** ヒットした位置 */
512+ private Position hitPosition = null;
404513
405- /** ヒットした見た目の行内の、ヒットした部分の情報 */
406- private TextHitInfo textHitInfo = null;
407-
408514 /** ヒットしたリンクの情報 */
409515 private LinkInfo hitLinkInfo = null;
410516
@@ -417,22 +523,14 @@
417523 }
418524
419525 /**
420- * lineLayoutIndex を取得します。
421- * @return lineLayoutIndex を返します。
526+ * hitPosition を取得します。
527+ * @return hitPosition を返します。
422528 */
423- public int getLineLayoutIndex() {
424- return lineLayoutIndex;
529+ public Position getHitPosition() {
530+ return hitPosition;
425531 }
426532
427533 /**
428- * textHitInfo を取得します。
429- * @return textHitInfo を返します。
430- */
431- public TextHitInfo getTextHitInfo() {
432- return textHitInfo;
433- }
434-
435- /**
436534 * hitLinkInfo を取得します。
437535 * @return hitLinkInfo を返します。
438536 */
@@ -449,22 +547,14 @@
449547 }
450548
451549 /**
452- * lineLayoutIndex をセットします。
453- * @param lineLayoutIndex セットしたい lineLayoutIndex の値
550+ * hitPosition をセットします。
551+ * @param hitPosition セットしたい hitPosition の値
454552 */
455- public void setLineLayoutIndex(int lineLayoutIndex) {
456- this.lineLayoutIndex = lineLayoutIndex;
553+ public void setHitPosition(Position hitPosition) {
554+ this.hitPosition = hitPosition;
457555 }
458556
459557 /**
460- * textHitInfo をセットします。
461- * @param textHitInfo セットしたい textHitInfo の値
462- */
463- public void setTextHitInfo(TextHitInfo textHitInfo) {
464- this.textHitInfo = textHitInfo;
465- }
466-
467- /**
468558 * hitLinkInfo をセットします。
469559 * @param hitLinkInfo セットしたい hitLinkInfo の値
470560 */
@@ -483,15 +573,17 @@
483573 private HitResult hitTest(float x, float y) {
484574 HitResult result = new HitResult();
485575
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) {
487578 // エリア外
488579 result.setHitArea(HitArea.NONE);
489580 } else {
490581 LineLayout lineLayout = null;
582+ int lineLayoutIndex = -1;
491583 for (int ix = lineLayouts.size() - 1; ix >= 0; --ix) {
492584 lineLayout = lineLayouts.get(ix);
493585 if (lineLayout.getTop() < y) {
494- result.setLineLayoutIndex(ix);
586+ lineLayoutIndex = ix;
495587 break;
496588 }
497589 }
@@ -506,10 +598,10 @@
506598 if (textLayout == null) {
507599 // 空行の場合
508600 result.setHitArea(HitArea.NONE);
509- result.setTextHitInfo(TextHitInfo.beforeOffset(0));
601+ result.setHitPosition(new Position(lineLayoutIndex, TextHitInfo.leading(0)));
510602 } else {
511603 TextHitInfo textHitInfo = textLayout.hitTestChar(x, y);
512- result.setTextHitInfo(textHitInfo);
604+ result.setHitPosition(new Position(lineLayoutIndex, textHitInfo));
513605 int lineIndex = lineLayout.getLineIndex();
514606 int charIndex = lineLayout.getStartCharIndex() + textHitInfo.getCharIndex();
515607
@@ -549,11 +641,75 @@
549641 }
550642
551643 /**
552- * マウスカーソルが動いたときにリスナーから呼ばれます。
553- * @param e マウスイベント
644+ * マウスイベントを処理している間の情報
554645 */
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());
557713 HitArea hitArea = hitResult.getHitArea();
558714 if (hitArea == HitArea.LINK) {
559715 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
@@ -565,10 +721,27 @@
565721 }
566722
567723 /**
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+ /**
568740 * リンクがクリックされたことを通知します。
569741 * @param link クリックされたリンク
742+ * @param mouseEvent 元となったマウスイベント
570743 */
571- private void fireLinkClicked(Link link) {
744+ private void fireLinkClicked(Link link, MouseEvent mouseEvent) {
572745 // Guaranteed to return a non-null array
573746 Object[] listeners = eventListenerList.getListenerList();
574747 // Process the listeners last to first, notifying
@@ -738,9 +911,44 @@
738911
739912 Graphics2D g2 = (Graphics2D)g;
740913 if (lineLayouts != null) {
741- for (LineLayout lineLayout : lineLayouts) {
914+ for (int lineLayoutIndex = 0; lineLayoutIndex < lineLayouts.size(); ++lineLayoutIndex) {
915+ LineLayout lineLayout = lineLayouts.get(lineLayoutIndex);
742916 TextLayout textLayout = lineLayout.getTextLayout();
743917 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();
744952 textLayout.draw(g2, messageAreaRect.x,
745953 lineLayout.getTop() + textLayout.getAscent());
746954 }
@@ -747,4 +955,94 @@
747955 }
748956 }
749957 }
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+ }
7501048 }
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/PeriodView.java (revision 4)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/PeriodView.java (revision 5)
@@ -25,6 +25,7 @@
2525
2626 package com.hironytic.moltonf.view;
2727
28+import java.awt.AWTEvent;
2829 import java.awt.Color;
2930 import java.awt.Component;
3031 import java.awt.FlowLayout;
@@ -32,7 +33,10 @@
3233 import java.awt.Graphics;
3334 import java.awt.Graphics2D;
3435 import java.awt.Point;
36+import java.awt.Rectangle;
37+import java.awt.event.MouseEvent;
3538 import java.awt.geom.Dimension2D;
39+import java.awt.geom.Point2D;
3640 import java.awt.geom.Rectangle2D;
3741 import java.util.Arrays;
3842 import java.util.List;
@@ -99,6 +103,9 @@
99103
100104 /** 強調表示設定 */
101105 private List<HighlightSetting> highlightSettingList;
106+
107+ /** 範囲選択用オブジェクト */
108+ private RangeSelector rangeSelector = new PeriodViewRangeSelector();
102109
103110 private MessageComponent nextDayLink; // TODO: MoltonfView できちんと実装すれば持つ必要ない
104111
@@ -129,6 +136,8 @@
129136 contentPanel.add(this);
130137 scrollPane = new ScrollPane(this);
131138 scrollPane.setViewportView(contentPanel);
139+
140+ enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
132141 }
133142
134143 /**
@@ -356,6 +365,7 @@
356365 talkView.setAreaWidth(500); //TODO:
357366 talkView.setHighlightSettingList(highlightSettingList);
358367 talkView.setFont(getFont());
368+ talkView.setRangeSelector(rangeSelector);
359369 TalkType talkType = talk.getTalkType();
360370 Avatar speaker = talk.getSpeaker();
361371 if (isMatchFilterOfTalk(talkType) && isMatchFilterOfSpeaker(speaker)) {
@@ -369,6 +379,7 @@
369379 storyEventView.setStoryEvent(storyEvent);
370380 storyEventView.setAreaWidth(500); // TODO:
371381 storyEventView.setFont(getFont());
382+ storyEventView.setRangeSelector(rangeSelector);
372383 EventFamily eventFamily = storyEvent.getEventFamily();
373384 if (isMatchFilterOfEvent(eventFamily)) {
374385 isVisible = true;
@@ -399,24 +410,6 @@
399410 add(nextDayLink);
400411 }
401412
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-
420413 /**
421414 * フィルタリング状態を再構成します。
422415 */
@@ -549,4 +542,168 @@
549542 g2d.fill(paintRect);
550543 g2d.setColor(oldColor);
551544 }
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+ }
552709 }
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/StoryEventView.java (revision 4)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/StoryEventView.java (revision 5)
@@ -35,6 +35,7 @@
3535 import java.awt.RenderingHints;
3636 import java.awt.Stroke;
3737 import java.awt.geom.Dimension2D;
38+import java.awt.geom.Point2D;
3839 import java.awt.geom.Rectangle2D;
3940
4041 import javax.swing.JComponent;
@@ -45,7 +46,7 @@
4546 * ストーリー中のイベントの表示を担当するビュー
4647 */
4748 @SuppressWarnings("serial")
48-public class StoryEventView extends JComponent implements MoltonfView {
49+public class StoryEventView extends JComponent implements MoltonfView, Selectable {
4950
5051 /** ビューの左の余白 */
5152 private static final float VIEW_PADDING_LEFT = 16;
@@ -235,4 +236,45 @@
235236
236237 eventMessageComponent.setFont(font);
237238 }
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+ }
238280 }
--- trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/RangeSelector.java (nonexistent)
+++ trunk/Sources/Moltonf/src/com/hironytic/moltonf/view/RangeSelector.java (revision 5)
@@ -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+}
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Show on old repository browser