Newer
Older
SimpleATN_M / src / main / java / jp / ac / kyutech / mns / ist / SimpleATN.java
@motoki miura motoki miura on 26 Apr 2022 38 KB first commit
package jp.ac.kyutech.mns.ist;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.TooManyListenersException;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

import jp.ac.kyutech.mns.ist.filter.RegionSSFilter;
import jp.ac.kyutech.mns.ist.plugin.DefaultPlugin;
import jp.ac.kyutech.mns.ist.plugin.WindowManagerPlugin;
import jp.ac.kyutech.mns.ist.sheetmap.SeatmapController;
import jp.ac.kyutech.mns.ist.util.FileReadWriter;
import jp.ac.kyutech.mns.ist.util.MemoryMonitor;
import jp.ac.kyutech.mns.ist.util.MultiDisplay;
import jp.ac.kyutech.mns.ist.util.MyAction;
import jp.ac.kyutech.mns.ist.util.MyPImage;
import jp.ac.kyutech.mns.ist.util.NetUtil;
import jp.ac.kyutech.mns.ist.util.NullFileFilter;
import jp.ac.kyutech.mns.ist.util.PrintPrintable;
import jp.ac.kyutech.mns.ist.util.SingleTableSelector;
import jp.ac.kyutech.mns.ist.util.WheelRotationHandler;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PLayer;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventFilter;
import edu.umd.cs.piccolo.event.PPanEventHandler;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PObjectOutputStream;
import edu.umd.cs.piccolo.util.PPaintContext;

// TODO
// FocusFrameをぴったりのサイズにする.とくに集約表示.先に推奨比率を調べる
// レイアウトのリファクタリング
// Macでもうごくように,なるべく同一のコードをつかう

/**
 * SimpleなAirTransNote.筆記を表示するだけ.
 * 右ドラッグ範囲にズーム,マウスホイールでズーム,
 * @author miuramo
 */
public class SimpleATN extends FullScreenableJFrame implements Runnable, PathDropActionListener, LayoutTarget {

	private static final long serialVersionUID = -8183529835329375441L;
	public static SimpleATN theapp;
	public static int zoommsec = 1000;

	public SimpleATN_MenuToolbarAction menutoolbar;
	public PLayer layer;
	public ATN_Mysql_DB mysqldb;
	public ATN_Mysql_DB mysqldb_root;
	boolean isNoDB = false;
	public DB_Lecture openingLecture;
	public String openingLectureid;
	ZoomRegionHandler zrh;//Event_onSheetが参照するため
	PPanEventHandler panEventHandler;

	DrawPenToolbar drawToolbar;

	public String dbuser="atnuser", dbpass="atnuser", dbname="atnrepos";

	int layoutcols = 0;
	PBounds tempAfterLayoutBounds = null;
	int filedialogopennum = 0;

	DropTarget dropTarget = null;
	public static boolean isMACOSX = false;
	static {
		String lcOSName = System.getProperty("os.name").toLowerCase();
		isMACOSX = lcOSName.startsWith("mac os x");
	}

	public TreeMap<Integer,Note> notes;
	public String pimagePath;//画像があるフォルダのパス.カレントディレクトリから探索し,複数あったら選択する.
	//		public ConfigPanel_PImageFinder pimagePathFinder;
	public Hashtable<Integer,MyPImage> pimages;
	//	public Hashtable<Integer,MyPImage> oldpimages;

	int current_cursornum = Cursor.DEFAULT_CURSOR;

	int sheetVisibleMode[] = new int[7];//{2,2,2,2,2,2,2,2}; //0:all hide, 1: only data, 2: all show
	SimpleATN_UDPNoticeReceiver updator;
	InsideToolbar_SheetVisible sheetVisibleToolbuttons;

	PText toolTipPText = null;

	ArrayList<DefaultPlugin> plugins;
	InspectorPlugin inspector;
	Event_onSATN event_onsatn;

	LayoutAbstract layoutabst;
	LayoutBySeat layoutseat;

	public String initArgFile;
	public String openingFile;

	public SimpleATN() {
		this(null,null);
	}
	public SimpleATN(String arg, int count){
		this(null,arg);
	}
	public void changeTitle(String file){
		openingFile = file;
		setTitle("SimpleATN ver "+SimpleATN.class.getPackage().getImplementationVersion() + " "+file);
	}
	
	public SimpleATN(final PCanvas aCanvas, final String file) {
		super("SimpleATN ver "+SimpleATN.class.getPackage().getImplementationVersion() + " "+file);//TODO:satnファイル読み込み時に修正する

		theapp = this;
		initArgFile = file;
		setBackground(null);
		setBounds(getDefaultFrameBounds());
		for(int i=1;i<7;i++) sheetVisibleMode[i] = 1;
		sheetVisibleMode[0]=2;

		try {
			setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		}
		catch (final SecurityException e) {
			System.out.println("Ignoring security exception. Assuming Applet Context.");
		}
		addWindowListener(new WindowListener(){
			public void windowActivated(WindowEvent arg0) {			}
			public void windowClosed(WindowEvent arg0) {
			}
			public void windowClosing(WindowEvent arg0) {
				ConfigWindow.configWin.filter.saveBeforeExit();
				System.exit(0);
			}
			public void windowDeactivated(WindowEvent arg0) {
			}
			public void windowDeiconified(WindowEvent arg0) {
			}
			public void windowIconified(WindowEvent arg0) {
			}
			public void windowOpened(WindowEvent arg0) {
			}
		});

		if (canvas == null) {
			this.canvas = new PCanvas();
			//マウスが動いたら,カーソルをもとに戻す
			this.canvas.addInputEventListener(new PBasicInputEventHandler() {
				public void mouseMoved(final PInputEvent aEvent) {
					popCursor();
					aEvent.setHandled(true);
				}
			});
		}

		canvas.setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
		canvas.setAnimatingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
		canvas.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);

		//		canvas.removeInputEventListener(canvas.getPanEventHandler());

		notes = new TreeMap<Integer, Note>();
		pimages = new Hashtable<Integer, MyPImage>();
		//		oldpimages = new Hashtable<Integer, MyPImage>();

		menutoolbar = new SimpleATN_MenuToolbarAction(this);
		menutoolbar.foldToolbarText(true);

		ConfigWindow.setVisible(this, false);

		checkSecondaryDisplay();//もし,セカンダリ画面があれば,アイコン選択状態にする.あとで解除可能.


		getContentPane().add(this.canvas, BorderLayout.CENTER);
		validate();
		setFullScreenMode(false);

		//		pimagePathFinder = new ConfigPanel_PImageFinder(this, new File(System.getProperty("user.dir")), SimpleATN_ConfigWindow.configWin);

		dropTarget = new DropTarget();
		try{
			dropTarget.addDropTargetListener(new PathDropTargetListener(this));
			getCanvas().setDropTarget(dropTarget);
		}catch(TooManyListenersException ex){
			ex.printStackTrace(System.out);
		}

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				SimpleATN.this.initialize(file);
				repaint();
			}
		});

		sheetVisibleToolbuttons = new InsideToolbar_SheetVisible(this);
		getCanvas().getCamera().addChild(sheetVisibleToolbuttons);

		InsideToolbar_ZoomHandle zh = new InsideToolbar_ZoomHandle(getCanvas().getCamera());
		getCanvas().getCamera().addChild(zh);
		InsideToolbar_PanHandle ph = new InsideToolbar_PanHandle(getCanvas().getCamera());
		getCanvas().getCamera().addChild(ph);

		updator = new SimpleATN_UDPNoticeReceiver(this);

		setVisible(true);
	}
	/**
	 * 画像フォルダの設定と,保存済み筆跡マップの読み込み
	 * @param f
	 */
	public void setBGImagePath(File f){
		// 		File f = new File("atntemplates");
		if (f != null && f.exists()){
			pimagePath = f.getAbsolutePath();
			System.out.println(pimagePath);
			pimages.clear();//一旦削除
			if (isMACOSX){
				for(int i=1;i<7;i++){
					byte[] b = FileReadWriter.readBytesFromFile(pimagePath+File.separator+"スライド"+i+".png");
					MyPImage pimg = new MyPImage(b, false);
					pimages.put(i,pimg);
				}
			} else {
				for(int i=1;i<7;i++){
					byte[] b = FileReadWriter.readBytesFromFile(pimagePath+File.separator+"スライド"+i+".PNG");
					MyPImage pimg = new MyPImage(b, false);
					pimages.put(i,pimg);
				}
			}
			//設定ファイルを読み込む
			ArrayList<String> setup = FileReadWriter.getLinesList(pimagePath+File.separator+"setup.txt", true);
			ArrayList<String> copy = new ArrayList<String>();
			for(String ts: setup){
				copy.add(ts);
			}
			boolean hasModification = false;
			for(String ts: copy){
				String s = ts.trim();
				if (s.startsWith("#")) continue;
				else if (s.length()<1) continue;
				else if (s.startsWith("seatmap")){
					int conf = JOptionPane.showConfirmDialog(this, "筆席マップ "+s+" を読み込みますか?\n\n「いいえ」をおすと,次回も尋ねます.\n「取消」をおすと,設定を削除し,今後は尋ねません.");
					if (conf == JOptionPane.YES_OPTION){
						new SeatmapController(s, SimpleATN.theapp);
					} else if (conf == JOptionPane.CANCEL_OPTION){
						setup.remove(ts);
						hasModification = true;
					}
				}
			}
			if (hasModification){
				FileReadWriter.putLines(this.pimagePath+File.separator+"setup.txt", setup);
			}
		} else {
			JOptionPane.showMessageDialog(this, "背景画像フォルダがみつかりませんでした");
		}
	}
	public void pushCursor(int cursornum){
		if (cursornum != current_cursornum){
			current_cursornum = cursornum;
			canvas.pushCursor(new Cursor(current_cursornum));
		}
	}
	public void popCursor(){
		canvas.popCursor();
		current_cursornum = Cursor.DEFAULT_CURSOR;
	}
	public void initialize(String file) {

		//remove default zoom event hander
		getCanvas().removeInputEventListener(getCanvas().getZoomEventHandler());
		getCanvas().removeInputEventListener(getCanvas().getPanEventHandler());

		// Button1 - Pan
		panEventHandler = new PPanEventHandler();
		panEventHandler.setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
		getCanvas().addInputEventListener(panEventHandler);

		// Button2 Wheel Rotation - Zoom
		WheelRotationHandler wrHandler = new WheelRotationHandler(getCanvas(), getCanvas().getCamera());
		wrHandler.setEventFilter(new PInputEventFilter(InputEvent.BUTTON2_MASK));
		getCanvas().addInputEventListener(wrHandler);

		// Button3 Drag - Zoom Region
		zrh = new ZoomRegionHandler(this.getCanvas());
		getCanvas().addInputEventListener(zrh);
		zrh.setEventFilter(new PInputEventFilter(InputEvent.BUTTON3_MASK));

		plugins = new ArrayList<DefaultPlugin>();
		plugins.add(new WindowManagerPlugin(getCanvas(), this));
		//プラグインとして,インスペクタを追加
		inspector = new InspectorPlugin(getCanvas(), this);
		plugins.add(inspector);

		event_onsatn = new Event_onSATN(this);
		getCanvas().getCamera().addInputEventListener(event_onsatn);

		getCanvas().getPanEventHandler().setEventFilter(new PInputEventFilter(InputEvent.BUTTON3_DOWN_MASK));

		//キー入力(ESCキーで画面全体表示)
		getCanvas().addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(KeyEvent e) {		}
			@Override
			public void keyReleased(KeyEvent e) {	}			
			@Override
			public void keyPressed(KeyEvent e) {
				//				System.out.println("keyCode=" + e.getKeyCode() + " keyChar ["+ e.getKeyChar() + "]");
				if (e.getKeyCode() == 27) {// ESC
					upview();
				}
				if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_F || e.getKeyCode() == KeyEvent.VK_R || e.getKeyCode() == KeyEvent.VK_M){
					if (zrh.selection != null){
						if (zrh.pressN != null && zrh.pressN instanceof Sheet){
							Sheet s = (Sheet)zrh.pressN;
							s.collect(zrh.selection, e.getKeyCode());
						}
						if (zrh.pressN != null && zrh.pressN instanceof SSGroup){ //SSGRoup だったら,シートに変換
							SSGroup ssg = (SSGroup)zrh.pressN;
							ssg.sheet.collect(zrh.selection, e.getKeyCode());
						}
					}
					zrh.dismissRegionLater();
				}
				if (e.getKeyCode() == KeyEvent.VK_Z){
					System.out.println("SimpleATN zoomLevel = "+getZoomLevel());
				}
				if (e.getKeyCode() == KeyEvent.VK_A || e.getKeyCode() == KeyEvent.VK_0){
					//					addNote();
					for(int i=0;i<7;i++){
						sheetVisibleMode[i]=2;
					}
					sheetVisibleToolbuttons.updateStatus();
					setSheetVisible(-1, 2);
				}
				if (e.getKeyCode() == KeyEvent.VK_F11){
					menutoolbar.getButton("Full Screen").doClick();
				}

				//範囲選択中なら...
				if (zrh.isDragging() && zrh.selection != null){
					if (KeyEvent.VK_1 <= e.getKeyCode() && e.getKeyCode() <= KeyEvent.VK_5){
						//領域フィルタに追加
						if (zrh.pressN != null && zrh.pressN instanceof Sheet){
							Sheet s = (Sheet)zrh.pressN;
							ConfigWindow.configWin.filter.addRegionFilter(s, zrh.selection, e.getKeyCode());
						}
						if (zrh.pressN != null && zrh.pressN instanceof SSGroup){ //SSGRoup だったら,シートに変換
							SSGroup ssg = (SSGroup)zrh.pressN;
							//							ssg.sheet.collect(zrh.selection, e.getKeyCode());
							ConfigWindow.configWin.filter.addRegionFilter(ssg.sheet, zrh.selection, e.getKeyCode());
						}
						zrh.dismissRegionLater();
					}
				} else {
					if (KeyEvent.VK_1 <= e.getKeyCode() && e.getKeyCode() <= KeyEvent.VK_6){
						//以前:1〜6の数字を入力したら,そのシートの表示モードの切り替え
						//					int sn = e.getKeyCode() - KeyEvent.VK_0;
						//					sheetVisibleMode[sn] = (sheetVisibleMode[sn]+1)%3;
						//					setSheetVisible(sn, sheetVisibleMode[sn]);
						//					sheetVisibleToolbuttons.updateStatus();
						//現在:そのシートのみを表示
						for(int i=0;i<7;i++){
							sheetVisibleMode[i]=0;
						}
						sheetVisibleToolbuttons.updateStatus();
						int sn = e.getKeyCode() - KeyEvent.VK_0;
						for(Note n: notes.values()) n.applySheetVisible();
						setSheetVisible(sn, 2);
					}
				}
				PBounds pb = getCanvas().getCamera().getViewBounds();
				int keyCode = e.getKeyCode();
				if (keyCode == 38) {// 上
					pb.moveBy(0, -pb.getHeight());
					zoomToBounds(pb,1000,"KeyPan");
				}
				if (keyCode == 37) {// 左
					pb.moveBy(-pb.getWidth(),0);
					zoomToBounds(pb,1000,"KeyPan");
				}
				if (keyCode == 39) {// 右
					pb.moveBy(pb.getWidth(),0);
					zoomToBounds(pb,1000,"KeyPan");
				}
				if (keyCode == 40) {// 下
					pb.moveBy(0,pb.getHeight());
					zoomToBounds(pb,1000,"KeyPan");
				}
			}
		});
		getCanvas().requestFocus();

		layer = getCanvas().getLayer();


		//最初にデータベースを読みに行く
		if (file==null){
			openConnection();
		} else {
//			loadFromFile(file);
		}
	}
	/**
	 * シートの表示・非表示.
	 * sn=sheet number, 0はペン番号, 1~6はシート, -1なら全て(ペン番号含む)
	 * mode=0は非表示,1は筆記があれば,2は常に
	 * @param sn
	 * @param mode
	 */
	public void setSheetVisible(int sn, int mode){
		if (sn > -1) sheetVisibleMode[sn] = mode;
		//		for(int i=0;i<7;i++) System.out.print(sheetVisibleMode[i]+" ");
		//		System.out.println("");
		setSheetVisible(sn, mode, true, true);
	}

	public void setSheetVisible(int sn, int mode, boolean doLayout, boolean showMessage){
		if (sn == -1){ //Apply to All sheets
			for(int i=0;i<7;i++){
				for(Note n: notes.values()){
					n.toggleVisible(i, mode);
				}
			}
			if (doLayout){
				relayout(1000, "SATN.setSheetVisible");
				zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.setSheetVisible");
			}
			if (showMessage){
				String m[] = {"を隠す","に書き込みがあれば表示","を表示"};
				showFadingMessage("すべてのシート "+m[mode], messageColor, Color.black, 2, 0.9f);
			}
		} else {
			for(Note n: notes.values()){
				n.toggleVisible(sn, mode);
			}
			if (doLayout){
				relayout(1000, "SATN.setSheetVisible");
				zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.setSheetVisible");
			}
			if (showMessage){
				String num = "シート "+String.valueOf(sn);
				if (sn==0) num="ペン番号";
				String m[] = {"を隠す","に書き込みがあれば表示","を表示"};
				if (sn==0) m[1] = "は表示すべきシートがあるときのみ表示";
				showFadingMessage(num+" "+m[mode], messageColor, Color.black, 2, 0.9f);
			}
		}
	}

	/**
	 * ズームアウト,レイアウト済みなら-1
	 * 表示領域の高さがノートの高さより,すこしでも大きければ0
	 * 表示領域の高さがノートの高さと同じなら1
	 * 表示領域の高さがノートの高さの70%以下なら,ズームイン=2
	 * @return
	 */
	public int getZoomLevel(){
		double h = getCanvas().getCamera().getViewBounds().getHeight();
		double w = getCanvas().getCamera().getViewBounds().getWidth();
		//		PBounds pb = notes.get(0).getGlobalFullBounds();

		PBounds gfb = getCanvas().getLayer().getGlobalFullBounds();
		if (Math.abs(gfb.getWidth()-w)<w*0.01 || Math.abs(gfb.getHeight()-h)<h*0.01) return -1; //もうレイアウト済み

		if (h > Note.height * 1.02){
			return 0;
		} else if (h < Note.height * 0.70){
			return 2;
		}
		return 1;
	}
	public void upview(){
		upview(getZoomLevel());
	}
	public void upview(int zoomLevel){
		if (zoomLevel == 2){
			Point2D centerP = getCanvas().getCamera().getViewBounds().getCenter2D();
			centerP = getCanvas().getCamera().localToGlobal(centerP);
			//中心点がふくまれる,Sheetを検索する.複数あったら最初のものを採用.
			Sheet sheet = findSheetAtGlobalPoint(centerP);
			if (sheet != null){
				Rectangle2D globalb = sheet.getGlobalBounds();
				zoomToBounds(globalb, 1000,"SATN.upview(int)");
			} else {
				zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.upview(int)");
			}
		} else if (zoomLevel == -1){
			relayout(1000, "SATN.upview(int)");
			zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.upview(int)");
		} else {
			zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.upview(int)");
		}
	}

	public Sheet findSheetAtGlobalPoint(Point2D p){
		@SuppressWarnings("unchecked")
		Collection<PNode> c = getCanvas().getLayer().getAllNodes();
		for(PNode n: c){
			if (n instanceof Sheet){
				Sheet s = (Sheet)n;
				if (s.getGlobalBounds().contains(p)){
					return s;
				}
			}
		}
		return null;
	}
	//	//全体画面を表示
	//	public void zoomHome(int msec){
	//		getCanvas().getCamera().animateViewToCenterBounds(layer.getGlobalFullBounds(),true, msec);//msecミリ秒かけてアニメーション
	//	}

	//called from DB import (initial)
	public Note getNote(int penid){
		Note n = notes.get(penid);
		if (n==null){
			n = addNote(penid);
		}
		return n;
	}
	//あいている番号を手当たり次第追加する
	public Note addNote(){
		int n = 1;
		while(true){ if (notes.get(n)==null) {return addNote(n);} n++; }
	}
	//ボタンで,あとから追加する(ここでは,表示シートの設定やレイアウトはしない)
	public Note addNote(int penid){
		if (notes.get(penid)!=null) return null;
		Note n = new Note(penid, this);
		notes.put(penid, n);
		getCanvas().getLayer().addChild(n);

		for(FocusFrame ff: FocusFrame.frames){
			ff.applyToNote(n,true);
		}
		return n;
	}


	public static void main(final String[] args) {
		if (args.length >= 2){
			StringBuffer sb = new StringBuffer();
			for(int i=1;i<args.length;i++){
				sb.append(args[i]+" ");
			}
			new SimpleATN(sb.toString().trim(), args.length);
		} else {
			new SimpleATN();
		}
	}

	public void openConnection() {
		if (mysqldb == null) mysqldb = new ATN_Mysql_DB(this, dbuser, dbpass, dbname);
		if (mysqldb == null) return;
		if (mysqldb_root == null) mysqldb_root = new ATN_Mysql_DB(this, "root", "root", dbname);
		if (mysqldb_root == null) return;

		ArrayList<DB_Lecture> dblec = mysqldb.get_db_lectures(true);
		if (dblec == null) return;
		if (dblec.size()==0) {
			JOptionPane.showMessageDialog(this, "筆記がありませんでした");
			return;
		}
		DB_Lecture selectedDBLec = null;
		if (dblec.size()>1){
			//			System.out.println(dblec.size());
			//			SingleSelector<DB_Lecture> sel = new SingleSelector<DB_Lecture>(dblec, this, "読み込む講義を選択してください");
			SingleTableSelector<DB_Lecture> sel = new SingleTableSelector<DB_Lecture>(dblec, this, "読み込む講義を選択してください",
					new String[]{"id","name","size","Start datetime","  "},
					new String[]{"id","name","dataamount","tstamp",""},
					new String[]{"start"},
					new int[]{30,300,60,200,100});
			if (sel.selectedObj == null) return;
			selectedDBLec = sel.selectedObj;
		} else {
			//TODO: もうここは使わない?
			selectedDBLec = dblec.get(0);
			if (selectedDBLec.dataamount==0){
				JOptionPane.showMessageDialog(this, "筆記がありませんでした");
				return;
			}
		}
		//もし新規読み取りで、継続データがあれば、名前をつけて保存する。
		if (selectedDBLec.dataamount == 0 && selectedDBLec == dblec.get(0)){
			if (dblec.get(1).id == 0 && dblec.get(1).dataamount > 0){
				//以前の筆記にテンポラリーな名前をつけて保存
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mmに保存");
				String name = JOptionPane.showInputDialog(this, "新規取り込みを開始するには、既存の筆記に名前をつけて保存してください\nキャンセルすると新規取り込みを中止します", sdf.format(System.currentTimeMillis()));
				if (name == null){
					return;
				}
				mysqldb.fixAsNamedLecture(name);		
			}
			showFadingMessage("新規取り込みを開始しました", SimpleATN.messageColor, Color.black, 2, 0.9f);
		}
		//もし以前に開いているノートがあれば,消す
		if (openingLecture != null) {
			openingLecture.stop();
			this.removeAllNote(true);
			//シートマップの登録も解除
			for(SeatmapController smap: ConfigWindow.configWin.seatmap.smaps){
				smap.clean();
			}
		}

		System.out.println("Loading "+selectedDBLec.toString());
		selectedDBLec.loadPenData(this, mysqldb);

		// TODO: grouping
		//		for(Note n: notes.values()){
		//			n.grouping(100);
		//		}

		openingLecture = selectedDBLec;
		openingLectureid = String.valueOf(selectedDBLec.id);
		menutoolbar.getAction("Reload").setEnabled(true);
		//		menutoolbar.getAction("Reload").setSelected(true);//UDP Notificationができたので自動で定期チェックはしない

		//ここで,フィルタがあれば読み込む TODO: 一旦削除
		/// RegionSSFilter.loadAfterLectureLoad();

		//		relayout(0);//最初のレイアウト
		//		zoomToBounds(tempAfterLayoutBounds,1000);
		Timer t = new Timer(1000, new SimpleATN_LayoutAction(this));
		t.setRepeats(false);
		t.start();
	}

	//OpenConnectionに失敗したとき
	public void findDBName(){
		//まずroot/rootで試みて,データベースを探す
		JOptionPane.showMessageDialog(this, "データベース探索機能,パスワード再設定機能は未実装です");
	}

	public void removeAllNote(boolean deleteff) {
		int p;
		if (!deleteff){
			p = JOptionPane.showConfirmDialog(this, "集約表示枠も削除する?\n(表示リセットをキャンセルするときは「取消」)","表示リセット",JOptionPane.YES_NO_CANCEL_OPTION);
			if (p == JOptionPane.CANCEL_OPTION) return;
		} else {
			p = JOptionPane.YES_OPTION;
		}
		if (p == JOptionPane.YES_OPTION) {
			for(FocusFrame ff: FocusFrame.frames){
				ff.dispose();
			}
			FocusFrame.frames.clear();
		}
		getCanvas().getLayer().removeAllChildren();
		notes.clear();
		//筆記モードなら,もとに戻す
		if (menutoolbar.getAction("Draw").isSelected()){
			menutoolbar.getAction("Draw").doClick();
			showFadingMessage("筆記モードを解除しました.\nDraw Mode has been reverted.", SimpleATN.messageColor, Color.black, 2, 0.9f);
		}

	}

	//	//テスト用.筆記をそれぞれのNoteのSheetに追加する
	//	public void addPNodeToEachNote(int sheetnum, Rectangle2D pb){
	//		for(Note n: notes.values()){
	//			Sheet s = n.sheets.get(sheetnum);
	//			s.addChild(new PPath(pb));
	//		}
	//	}
	@Override 
	public void run() {
		try {Thread.sleep(1200);} catch (InterruptedException e) {	e.printStackTrace();}
		for(FocusFrame ff: FocusFrame.frames) ff.updateCameraView();
	}
	public ArrayList<LayoutContent> getLayoutContents(){
		ArrayList<LayoutContent> cnt = new ArrayList<LayoutContent>();
		cnt.addAll(notes.values());
		return cnt;
	}
	public void relayout(int msec, String caller){
		boolean finished = false;
		//筆跡マップが有効か?
		MyAction seatmap;
		if (menutoolbar != null){
			seatmap = menutoolbar.getAction("SheetMap");
			if (seatmap != null && seatmap.isSelected()){
				if (ConfigWindow.configWin.seatmap.active_smap != null){
					layoutseat = new LayoutBySeat(getCanvas().getCamera(), ConfigWindow.configWin.seatmap.active_smap);
					layoutseat.layout(0, this);
					tempAfterLayoutBounds = layoutseat.contentregion(this);
					finished = true;
				}
			}
		}
		if (!finished){
			layoutabst = new LayoutAbstract(getCanvas().getCamera());
			layoutabst.layout(0, this);
			tempAfterLayoutBounds = layoutabst.contentregion(this);
			//最適な配置を考える
			//		System.out.println("relayout by "+caller);
			//			tempAfterLayoutBounds = new PBounds();
			//			int gap = 0;
			//			ArrayList<Note> rects = new ArrayList<Note>();
			//			rects.addAll(notes.values());
			//			int cols = getBestFitLayout(rects, getCanvas().getCamera().getViewBounds(), gap, gap);
			//			ArrayList<PActivity> palist = layout(rects, cols, gap, gap, msec, tempAfterLayoutBounds);
			//			layoutcols = cols;
			//			long mtime = System.currentTimeMillis();
			//			for (PActivity pact : palist) if (pact != null) pact.setStartTime(mtime);

		}
		// update FocusFrame view
		Thread t = new Thread(this);
		t.start();
		//もしペン筆記到来からの再レイアウト要求でなければ
		if (!caller.equals("DB_Lec.loadPenData")){
			for(Note n: notes.values()){
				for(Sheet s: n.sheets.values()){
					s.showFadingSheetNum(2000);
				}
			}
		}
	}
	/**
	 * シートマップでレイアウト
	 * @param stu2pos
	 */
	public void layoutBySeatmap() {
		if (layoutseat == null){
			if (ConfigWindow.configWin.seatmap.active_smap != null){
				layoutseat = new LayoutBySeat(getCanvas().getCamera(), ConfigWindow.configWin.seatmap.active_smap);
			}
		}
		layoutseat.layout(0, this);
		tempAfterLayoutBounds = layoutseat.contentregion(this);
		zoomToBounds(tempAfterLayoutBounds,1000,"satn.layoutBySeatmap");
	}

	public void zoomToBounds(Rectangle2D pb, int msec, String caller){
		zoomToBounds(pb, msec, caller, null);
	}
	public void zoomToBounds(Rectangle2D pb, int msec, String caller, PInputEvent aEvent) {
		if (pb != null) {
			//			System.out.println("zoom to "+pb.toString()+" by "+caller);
			new SimpleATN_animateCameraView(getCanvas().getCamera(), pb, msec, aEvent);
		}
	}

	public static void openMemoryMonitor() {
		final MemoryMonitor demo = new MemoryMonitor();
		final JFrame f = new JFrame("MemoryMonitor");
		WindowListener l = new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				demo.surf.stop();
				f.dispose();
			}
			public void windowDeiconified(WindowEvent e) {
				demo.surf.start();
			}

			public void windowIconified(WindowEvent e) {
				demo.surf.stop();
			}
		};

		f.addWindowListener(l);
		f.getContentPane().add(demo, BorderLayout.CENTER);
		f.pack();
		f.setSize(new Dimension(200, 200));
		f.setVisible(true);
		f.setLocation(965, 1);
		demo.surf.start();
	}


	public void setDrawMode(boolean b) {
		if (drawToolbar == null) drawToolbar = new DrawPenToolbar();
		if (b){
			panEventHandler.setEventFilter(new PInputEventFilter(InputEvent.BUTTON2_MASK));//本当は効果を消したい
			PCamera cam = getCanvas().getCamera();
			cam.addChild(drawToolbar);
			drawToolbar.setOffset((cam.getWidth()-drawToolbar.getWidth())/2,0);
			if (SimpleATN.theapp.isNoDB){
				SimpleATN.theapp.showFadingMessage("DB未接続です.筆記を保存するにはファイルに保存してください", SimpleATN.messageColor, Color.black, 2, 0.9f);
			}

		} else {
			panEventHandler.setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
			drawToolbar.removeFromParent();
		}
		for(Note n: notes.values()){
			for(Sheet s: n.sheets.values()){
				s.setDrawMode(b);
			}
		}	
	}

	public String selectfile(boolean issave, String ext){
		//		ファイルダイアログを開く
		String cdir = System.getProperty("user.dir");
		JFileChooser fd = new JFileChooser(cdir);
		NullFileFilter nff = new NullFileFilter(ext);
		fd.setFileFilter(nff);
		int returnval;
		if (issave) returnval = fd.showSaveDialog(this);
		else returnval = fd.showOpenDialog(this);
		if (returnval == JFileChooser.APPROVE_OPTION){
			// 拡張子がついていないとき,むりやりつける
			StringBuffer path = new StringBuffer(fd.getSelectedFile().getAbsolutePath());
			if (!path.toString().endsWith(ext)){
				path.append(ext);
			}
			return path.toString();
		}else{
			return null;
		}
	}
	public void saveToFile(String fn) {
		if (fn == null) fn = selectfile(true,".satn");
		if (fn == null) return;//もしファイル名を選択しなかったら
		FileReadWriter.writeBytesToFile(fn, byteSerializeExport(true));
		File f = new File(fn);
		showFadingMessage(f.getName()+"\nに保存しました", messageColor, Color.black, 2, 0.9f);
	}
	public void saveFrameToFile(String fn) {
		if (fn == null) fn = selectfile(true,".waku");
		if (fn == null) return;//もしファイル名を選択しなかったら
		FileReadWriter.writeBytesToFile(fn, byteSerializeExport(false));
		File f = new File(fn);
		showFadingMessage("領域データを "+f.getName()+"\nに保存しました", messageColor, Color.black, 2, 0.9f);
	}

	public void loadFromFile(String fn,int num){
		if (filedialogopennum == 2 && num==2) return;
		System.out.println(num); filedialogopennum = num;
		if (fn == null) fn = selectfile(false,".satn");
		if (fn == null) return;//もしファイル名を選択しなかったら
		removeAllNote(true);
		byte[] ba = FileReadWriter.readBytesFromFile(fn);
		byteSerializeImport(ba,true);

		File f = new File(fn);
		showFadingMessage(f.getName()+"\nから読み込みました", messageColor, Color.black, 2, 0.9f);
		changeTitle(f.getAbsolutePath());
		openingLectureid = f.getName();
		//ここで,フィルタがあれば読み込む
		RegionSSFilter.loadAfterLectureLoad();

		relayout(1000, "SimpleATN.loadFromFile");
		zoomToBounds(tempAfterLayoutBounds, 1000, "DB_Lec.loadPenData");
	}
	public void loadFrameFromFile(String fn){
		if (fn == null) fn = selectfile(false,".waku");
		if (fn == null) return;//もしファイル名を選択しなかったら
		byte[] ba = FileReadWriter.readBytesFromFile(fn);
		byteSerializeImport(ba,false);

		File f = new File(fn);
		showFadingMessage("領域データを "+f.getName()+"\nから読み込みました", messageColor, Color.black, 2, 0.9f);
	}
	public transient static Color messageColor = new Color(204,238,255);


	public byte[] byteSerializeExport(boolean waku_all) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		OutputStream gzipos = null;
		PObjectOutputStream pobjos = null; // PNodeを保存するときはPObjectOutputStream

		try {
			gzipos = new GZIPOutputStream(baos);
			pobjos = new PObjectOutputStream(gzipos);

			if (waku_all){ // collect target nodes
				pobjos.writeInt(notes.size());
				System.out.println("Note size "+notes.size());
				for(Note n: notes.values()){
					pobjos.writeInt(n.getAll().size());
					System.out.println("Note "+n.penid+"  SS size "+n.getAll().size());
					for(ShortStroke ss: n.getAll()){
						ss.penid = n.penid;
						pobjos.writeObjectTree(ss);
					}
				}

				pobjos.writeInt(pimages.size());
				System.out.println("PImages size "+pimages.size());
				for(int i=1;i<=pimages.size();i++){
					pobjos.writeObjectTree(pimages.get(i));
				}
			}
			pobjos.close();
			gzipos.close();
		} catch (IOException iex) {
			iex.printStackTrace(System.out);
		}
		System.out.println("save size: " + baos.size() + " bytes.");
		return baos.toByteArray();
	}
	public void byteSerializeImport(byte[] ba, boolean waku_all){
		ByteArrayInputStream bais = new ByteArrayInputStream(ba);
		InputStream gzipis = null;
		ObjectInputStream ois = null;
		PNode pn = null;
		try {
			gzipis = new GZIPInputStream(bais);
			ois = new ObjectInputStream(gzipis);
			if (waku_all){
				int num = ois.readInt();
				for(int i=0;i<num;i++){
					int ssnum = ois.readInt();
					for(int j=0;j<ssnum;j++){
						pn = (PNode) ois.readObject();
						if (pn instanceof ShortStroke){
							ShortStroke ss = (ShortStroke)pn;
							ss.addEventOnSS();
							this.getNote(ss.penid).addStroke(ss);
						}
					}
				}
				int pinum = ois.readInt();
				if (pinum > 0) pimages.clear();
				for(int i=1;i<=pinum;i++){
					pn = (PNode) ois.readObject();
					if (pn instanceof MyPImage){
						MyPImage ss = (MyPImage)pn;
						pimages.put(i, ss);
					}
				}
			}
			ois.close();
			gzipis.close();
			bais.close();
		} catch (EOFException eofex) {
			eofex.printStackTrace();
		} catch (IOException excep) {
			excep.printStackTrace(System.err);
		} catch (ClassNotFoundException excnf) {
			System.err.println("ClassNotFound Error");
		} finally {
		}
	}

	public void printToPDF() {
		//		setSize(592,876);
		String sht = JOptionPane.showInputDialog(this,"(1/2) 印刷するシート番号(1-6)を入力してください。");
		int shtnum = 0;
		if (sht == null) return;
		try{
			shtnum = Integer.parseInt(sht);
		} catch(NumberFormatException nfex){
			return;
		}
		if (shtnum == 0) return;
		if (menutoolbar.getAction("Landscape Mode").isSelected()) {
			setSize(876-90,572+90);
		} else {
			setSize(572,876);
		}
		//ハンドルの消去
		for(Object o: getCanvas().getCamera().getAllNodes()){
			if (o instanceof PNode){
				PNode pn = (PNode)o;
				if (pn instanceof InsideToolbar_ZoomHandle){
					pn.setVisible(false);
				} else if (pn instanceof InsideToolbar_PanHandle){
					pn.setVisible(false);
				}
			}
		}
		validate();
		//非表示ノートチェック
		boolean noshowsheet_exist = false;
		for(Note n: notes.values()){
			Sheet s = n.getSheet(shtnum);
			if (!s.getVisible()) noshowsheet_exist = true;
		}
		if (noshowsheet_exist) {
			int res = JOptionPane.showConfirmDialog(this, "非表示設定のシートがあります.それらは印刷スキップされませんが,続けてよろしいですか?","非表示シート",JOptionPane.YES_NO_OPTION);
			if (res == JOptionPane.NO_OPTION) return;
		}

		String ret = JOptionPane.showInputDialog(this,"(2/2) 出力するPDFファイル名を入力してください。");
		if (ret == null) return;
		if (!ret.endsWith(".pdf")){
			ret = ret + ".pdf";
		}

		PrintPrintable pdfprint = new PrintPrintable();
		if (menutoolbar.getAction("Landscape Mode").isSelected()) {
			pdfprint.prePrintByInstance(getCanvas(), "A4L", ret);
		} else {
			pdfprint.prePrintByInstance(getCanvas(), "A4P", ret);
		}
		ArrayList<Sheet> targetSheet = new ArrayList<Sheet>();
		for(Note n: notes.values()){
			Sheet s = n.getSheet(shtnum);
			if (s.getVisible()) targetSheet.add(s);
			s.prepare_for_print(true);//true=before
			n.prepare_for_print(true);
		}
		for(Sheet s: targetSheet){
			if (menutoolbar.getAction("Landscape Mode").isSelected()) {
				//調整なし
				PBounds b = s.getGlobalBounds();
				getCanvas().getCamera().setViewBounds(b);
				pdfprint.printPageByInstance();
			} else {
				PBounds b = s.getGlobalBounds();
				b.setOrigin(b.getX()-20, b.getY()-20);
				getCanvas().getCamera().setViewBounds(b);
				pdfprint.printPageByInstance();
			}
		}
		for(Sheet s: targetSheet){
			s.prepare_for_print(false);//false = after
		}
		for(Note n: notes.values()){
			n.prepare_for_print(false);
		}
		pdfprint.postPrintByInstance();
		//ハンドルの再表示
		for(Object o: getCanvas().getCamera().getAllNodes()){
			if (o instanceof PNode){
				PNode pn = (PNode)o;
				if (pn instanceof InsideToolbar_ZoomHandle){
					pn.setVisible(true);
				} else if (pn instanceof InsideToolbar_PanHandle){
					pn.setVisible(true);
				}
			}
		}

		showFadingMessage("PDF出力が完了しました", messageColor, Color.black, 2, 0.9f);

		NetUtil.openPDF(ret);
		zoomToBounds(tempAfterLayoutBounds, 1000, "SATN.printToPDF");
		NetUtil.openpath(".");
	}

	//ファイルのドラッグ&ドロップ
	@Override
	public void dropPath(String path, DropTargetDropEvent dtde) {
		System.out.println(path);
		if (path.endsWith(".satn")){
			loadFromFile(path,99);
		}
		if (path.endsWith(".waku")){
			loadFrameFromFile(path);
		}
		File f = new File(path);
		if (f.isDirectory()){
			//			System.out.println("Load Image");
			for(int i=1;i<7;i++){
				byte[] b = FileReadWriter.readBytesFromFile(path+File.separator+"スライド"+i+".PNG");
				MyPImage pimg = new MyPImage(b, false);
				pimages.put(i,pimg);
			}
			showFadingMessage("画像を読み込みました", messageColor, Color.black, 2, 0.9f);
		}
	}

	//画面(Camera)の左上に,ペン番号とシート番号を表示(b==trueのとき)
	public void showSheetInfoOnCamera(Sheet sheet, boolean b) {
		//		if (sheetVisibleMode[0]==0){
		//			System.out.println("Tooltip "+sheetVisibleMode[0]);
		//					getCanvas().requestFocus();
		if (toolTipPText == null) {
			toolTipPText = new PText();
			toolTipPText.setScale(2f);
			getCanvas().getCamera().addChild(toolTipPText);
		}
		toolTipPText.setText(" "+sheet.getNote().penid+" - "+sheet.num+" ");
		toolTipPText.setTextPaint(Sheet.numPTColorAry[sheet.num]);
		toolTipPText.setVisible(b);
		//		}
	}

	public void checkSecondaryDisplay(){
		ArrayList<Rectangle> dispList = MultiDisplay.getMultiDisplayRectList();
		if (dispList.size()>1){
			menutoolbar.getAction("Secondary Display").setSelected(true);
		}
	}
	public void showFrameOnSecondaryDisplay(JFrame f) {
		//thisとは別のディスプレイ(true)に,全画面で表示する(true)
		MultiDisplay.moveWindow(this, f, true, true);
	}

	//追加機能分のメニュー
	public void addPluginMenuTo(JPopupMenu menu) {
		for(DefaultPlugin dp: plugins){
			dp.addMenu(menu);
		}
	}

	public void setLandscapeMode(boolean b, boolean showMessage, boolean doLayout) {
		for(Note n: notes.values()){
			n.setLandscapeMode(b);
		}
		if (showMessage){
			if (b) {
				showFadingMessage("横長モード", messageColor, Color.black, 2, 0.9f);
			} else {
				showFadingMessage("縦長モード", messageColor, Color.black, 2, 0.9f);
			}
		}
		if (doLayout){
			relayout(1000, "SATN.setSheetVisible");
			zoomToBounds(tempAfterLayoutBounds, 1000,"SATN.setSheetVisible");
		}
	}
	public void applyLandscapeMode(){
		setLandscapeMode(menutoolbar.getAction("Landscape Mode").isSelected(), false, false);
	}

	public void applyDrawingMode() {
		for(Note n: notes.values()) n.applyDrawingMode();
	}

}