001: /**
002: * LibreSource
003: * Copyright (C) 2004-2008 Artenum SARL / INRIA
004: * http://www.libresource.org - contact@artenum.com
005: *
006: * This file is part of the LibreSource software,
007: * which can be used and distributed under license conditions.
008: * The license conditions are provided in the LICENSE.TXT file
009: * at the root path of the packaging that enclose this file.
010: * More information can be found at
011: * - http://dev.libresource.org/home/license
012: *
013: * Initial authors :
014: *
015: * Guillaume Bort / INRIA
016: * Francois Charoy / Universite Nancy 2
017: * Julien Forest / Artenum
018: * Claude Godart / Universite Henry Poincare
019: * Florent Jouille / INRIA
020: * Sebastien Jourdain / INRIA / Artenum
021: * Yves Lerumeur / Artenum
022: * Pascal Molli / Universite Henry Poincare
023: * Gerald Oster / INRIA
024: * Mariarosa Penzi / Artenum
025: * Gerard Sookahet / Artenum
026: * Raphael Tani / INRIA
027: *
028: * Contributors :
029: *
030: * Stephane Bagnier / Artenum
031: * Amadou Dia / Artenum-IUP Blois
032: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
033: */package org.libresource.so6.application.studio;
034:
035: import jlibdiff.vdiff.VDiff;
036:
037: import org.libresource.so6.core.WsConnection;
038: import org.libresource.so6.core.engine.DBType;
039: import org.libresource.so6.core.engine.util.FileUtils;
040: import org.libresource.so6.core.exec.ChangeType;
041: import org.libresource.so6.core.exec.FindConflict;
042: import org.libresource.so6.core.exec.ui.ConflictEditor;
043: import org.libresource.so6.core.exec.ui.Rename;
044: import org.libresource.so6.core.interfaces.FileTreeActionListener;
045:
046: import java.awt.BorderLayout;
047: import java.awt.Color;
048: import java.awt.Component;
049: import java.awt.Image;
050: import java.awt.Toolkit;
051: import java.awt.event.ActionEvent;
052: import java.awt.event.ActionListener;
053: import java.awt.event.MouseAdapter;
054: import java.awt.event.MouseEvent;
055: import java.awt.event.WindowAdapter;
056: import java.awt.event.WindowEvent;
057:
058: import java.io.File;
059: import java.io.IOException;
060: import java.io.InputStream;
061:
062: import java.util.ArrayList;
063: import java.util.Collection;
064: import java.util.Enumeration;
065: import java.util.Hashtable;
066: import java.util.Iterator;
067: import java.util.Properties;
068:
069: import javax.swing.BoxLayout;
070: import javax.swing.ImageIcon;
071: import javax.swing.JFrame;
072: import javax.swing.JLabel;
073: import javax.swing.JMenu;
074: import javax.swing.JMenuItem;
075: import javax.swing.JOptionPane;
076: import javax.swing.JPanel;
077: import javax.swing.JPopupMenu;
078: import javax.swing.JScrollPane;
079: import javax.swing.JTree;
080: import javax.swing.tree.DefaultMutableTreeNode;
081: import javax.swing.tree.DefaultTreeModel;
082: import javax.swing.tree.TreeCellRenderer;
083: import javax.swing.tree.TreeNode;
084:
085: /**
086: * @author smack
087: */
088: public class ConnectionFileTree extends JPanel {
089: private JTree tree;
090: private TreePopup popup;
091: private WsConnection wsc;
092: private FileTreeModel treeModel;
093: private TreeNode root;
094: private FileTreeActionListener treeActionListener;
095:
096: public ConnectionFileTree(String wscPath) throws Exception {
097: super (new BorderLayout());
098: wsc = new WsConnection(wscPath);
099: popup = new TreePopup("Action");
100:
101: //
102: root = new DefaultMutableTreeNode(new MetaFile(wsc.getPath()));
103: treeModel = new FileTreeModel(root, wsc);
104: tree = new JTree(treeModel);
105: tree.setCellRenderer(new MetaFileTreeRenderer());
106: tree.addMouseListener(new PopupListener());
107:
108: //
109: add(new JScrollPane(tree), BorderLayout.CENTER);
110: }
111:
112: public static void main(String[] args) throws Exception {
113: JFrame f = new JFrame("File tree view : " + args[0]);
114: ConnectionFileTree cft = new ConnectionFileTree(args[0]);
115: cft
116: .setFileTreeActionListener(cft.new DefaultFileTreeActionListener());
117: f.getContentPane().add(cft);
118: f.addWindowListener(new WindowAdapter() {
119: public void windowClosing(WindowEvent e) {
120: System.exit(0);
121: }
122: });
123: f.setSize(400, 400);
124: f.setLocation(((int) Toolkit.getDefaultToolkit()
125: .getScreenSize().getWidth() - f.getWidth()) / 2,
126: ((int) Toolkit.getDefaultToolkit().getScreenSize()
127: .getHeight() - f.getHeight()) / 2);
128: f.setVisible(true);
129: }
130:
131: public void setFileTreeActionListener(
132: FileTreeActionListener treeActionListener) {
133: this .treeActionListener = treeActionListener;
134: }
135:
136: class TreePopup extends JPopupMenu implements ActionListener {
137: private JMenuItem openHistory;
138: private JMenuItem diff;
139: private JMenuItem editConflict;
140: private JMenuItem revert;
141: private JMenuItem rename;
142: private JMenu changeType;
143: private JMenuItem changeTypeBin;
144: private JMenuItem changeTypeTxt;
145: private JMenuItem changeTypeXml;
146:
147: public TreePopup(String label) {
148: super (label);
149: openHistory = new JMenuItem("Open history");
150: openHistory.setActionCommand("HISTORY");
151: openHistory.addActionListener(this );
152: add(openHistory);
153: diff = new JMenuItem("Show diff with local ref");
154: diff.setActionCommand("DIFF");
155: diff.addActionListener(this );
156: add(diff);
157: editConflict = new JMenuItem("Edit conflict");
158: editConflict.setActionCommand("CONFLICT");
159: editConflict.addActionListener(this );
160: add(editConflict);
161: addSeparator();
162: rename = new JMenuItem("Rename local file");
163: rename.setActionCommand("RENAME");
164: rename.addActionListener(this );
165: add(rename);
166: changeType = new JMenu("Change Type");
167: changeTypeBin = new JMenuItem("to binary");
168: changeTypeBin.setActionCommand("CHT BIN");
169: changeTypeBin.addActionListener(this );
170: changeType.add(changeTypeBin);
171: changeTypeTxt = new JMenuItem("to text");
172: changeTypeTxt.setActionCommand("CHT TXT");
173: changeTypeTxt.addActionListener(this );
174: changeType.add(changeTypeTxt);
175: changeTypeXml = new JMenuItem("to XML");
176: changeTypeXml.setActionCommand("CHT XML");
177: changeTypeXml.addActionListener(this );
178: changeType.add(changeTypeXml);
179: add(changeType);
180: addSeparator();
181: revert = new JMenuItem("Undo local change");
182: revert.setActionCommand("REVERT");
183: revert.addActionListener(this );
184: add(revert);
185: }
186:
187: public void computeView(MetaFile metaFile) {
188: openHistory.setEnabled(false);
189: editConflict.setEnabled(false);
190: revert.setEnabled(false);
191:
192: switch (metaFile.getChangeType()) {
193: case MetaFile.CHANGE_TYPE_NEW_FILE:
194: break;
195:
196: case MetaFile.CHANGE_TYPE_REMOVED_FILE:
197: case MetaFile.CHANGE_TYPE_UPDATED_FILE:
198: revert.setEnabled(true);
199:
200: break;
201: }
202:
203: changeType.setEnabled(false);
204: changeTypeBin.setEnabled(false);
205: changeTypeTxt.setEnabled(false);
206: changeTypeXml.setEnabled(false);
207:
208: switch (metaFile.getFileType()) {
209: case MetaFile.FILE_TYPE_BIN:
210: changeType.setEnabled(true);
211: changeTypeTxt.setEnabled(true);
212: changeTypeXml.setEnabled(true);
213:
214: break;
215:
216: case MetaFile.FILE_TYPE_TXT:
217: changeType.setEnabled(true);
218: changeTypeBin.setEnabled(true);
219: changeTypeXml.setEnabled(true);
220:
221: break;
222:
223: case MetaFile.FILE_TYPE_XML:
224: changeType.setEnabled(true);
225: changeTypeBin.setEnabled(true);
226: changeTypeTxt.setEnabled(true);
227:
228: break;
229: }
230:
231: if (metaFile.getFileType() == MetaFile.FILE_TYPE_TXT) {
232: openHistory.setEnabled(true);
233: }
234:
235: if (metaFile.isConflict()) {
236: editConflict.setEnabled(true);
237: }
238: }
239:
240: public void actionPerformed(ActionEvent e) {
241: String command = e.getActionCommand();
242: String selectedPath = ((MetaFile) ((DefaultMutableTreeNode) tree
243: .getSelectionPath().getLastPathComponent())
244: .getUserObject()).getPath();
245:
246: if (selectedPath == null) {
247: return;
248: }
249:
250: try {
251: if (command.equals("REVERT")) {
252: FileUtils.copy(new File(wsc.getRefCopyPath(),
253: selectedPath), new File(wsc.getPath(),
254: selectedPath));
255: treeModel.compute();
256: }
257:
258: if (command.startsWith("CHT ")) {
259: String type = command.substring(4);
260: ChangeType ct = new ChangeType(wsc.getDataPath()
261: + File.separator
262: + WsConnection.SO6_WSC_FILE, selectedPath,
263: type);
264: treeModel.compute();
265: }
266:
267: if (treeActionListener != null) {
268: if (command.equals("CONFLICT")) {
269: treeActionListener.editConflictOnFile(wsc
270: .getPath()
271: + File.separator + selectedPath);
272: }
273:
274: if (command.equals("HISTORY")) {
275: treeActionListener.showFileHistory(wsc,
276: selectedPath);
277: }
278:
279: if (command.equals("DIFF")) {
280: treeActionListener.diff(wsc, selectedPath);
281: }
282:
283: if (command.equals("RENAME")) {
284: treeActionListener.rename(wsc, selectedPath);
285: }
286: } else {
287: System.out.println("No tree action listener set : "
288: + command);
289: }
290: } catch (Exception e1) {
291: JOptionPane.showMessageDialog(this , "Error on command "
292: + command + " with file " + selectedPath
293: + " : " + e1.getMessage());
294: }
295: }
296: }
297:
298: class PopupListener extends MouseAdapter {
299: public void mousePressed(MouseEvent e) {
300: maybeShowPopup(e);
301: }
302:
303: public void mouseReleased(MouseEvent e) {
304: maybeShowPopup(e);
305:
306: if (e.getClickCount() == 2) {
307: try {
308: treeModel.compute();
309: } catch (Exception e1) {
310: e1.printStackTrace();
311: }
312: }
313: }
314:
315: private void maybeShowPopup(MouseEvent e) {
316: tree.setSelectionPath(tree.getPathForLocation(e.getX(), e
317: .getY()));
318:
319: if ((tree.getSelectionPath() != null)
320: && e.isPopupTrigger()
321: && (tree.getSelectionPath().getLastPathComponent() != null)) {
322: popup
323: .computeView((MetaFile) ((DefaultMutableTreeNode) tree
324: .getSelectionPath()
325: .getLastPathComponent())
326: .getUserObject());
327: popup.show(e.getComponent(), e.getX(), e.getY());
328: }
329: }
330: }
331:
332: public class MetaFile extends File {
333: public final static int FILE_TYPE_DIRECTORY = DBType.TYPE_DIR;
334: public final static int FILE_TYPE_TXT = DBType.TYPE_FILE_TXT;
335: public final static int FILE_TYPE_XML = DBType.TYPE_FILE_XML;
336: public final static int FILE_TYPE_BIN = DBType.TYPE_FILE_BIN;
337: public final static int CHANGE_TYPE_NEW_FILE = 1;
338: public final static int CHANGE_TYPE_REMOVED_FILE = 2;
339: public final static int CHANGE_TYPE_UPDATED_FILE = 3;
340: private boolean conflict;
341: private int fileType;
342: private int changeType;
343:
344: public MetaFile(String path) {
345: super (path);
346: setFileType(FILE_TYPE_DIRECTORY);
347: }
348:
349: public int getChangeType() {
350: return changeType;
351: }
352:
353: public boolean isConflict() {
354: return conflict;
355: }
356:
357: public int getFileType() {
358: return fileType;
359: }
360:
361: public void setChangeType(int i) {
362: changeType = i;
363: }
364:
365: public void setConflict(boolean b) {
366: conflict = b;
367: }
368:
369: public void setFileType(int i) {
370: fileType = i;
371: }
372:
373: public void update(MetaFile metaFile) {
374: setFileType(metaFile.getFileType());
375: setChangeType(metaFile.getChangeType());
376: setConflict(metaFile.isConflict());
377: }
378: }
379:
380: public class FileTreeModel extends DefaultTreeModel {
381: private WsConnection wsc;
382: private Hashtable treeNodeIndex;
383: private String basePath;
384: private Collection conflictFiles;
385:
386: public FileTreeModel(TreeNode root, WsConnection wsc)
387: throws Exception {
388: super (root);
389: this .wsc = wsc;
390: basePath = wsc.getPath().replaceAll("\\\\", "/");
391: this .treeNodeIndex = new Hashtable();
392: compute();
393: }
394:
395: public void compute() throws Exception {
396: conflictFiles = new ArrayList();
397: wsc.getDBType().load();
398: wsc.getRefCopy().getDBType().load();
399: wsc.getDBType().updateFromWalk(wsc.getPath(),
400: wsc.getXmlAutoDetection()); // done
401: // in
402: // search
403: // conflict
404:
405: // first search for conflict
406: Collection c = FindConflict.searchConflict(wsc
407: .getDataPath()
408: + File.separator + WsConnection.SO6_WSC_FILE);
409:
410: for (Iterator i = c.iterator(); i.hasNext();) {
411: conflictFiles
412: .add(((FindConflict.ConflictFile) i.next())
413: .getPath());
414: }
415:
416: //
417: this .treeNodeIndex.clear();
418: ((DefaultMutableTreeNode) this .getRoot())
419: .removeAllChildren();
420:
421: Properties props = wsc.getDBType().getDBTypeData();
422:
423: for (Enumeration e = props.keys(); e.hasMoreElements();) {
424: String key = (String) e.nextElement();
425:
426: if ((key.trim().length() > 0)
427: && !key.trim().equals(basePath)) {
428: MetaFile metaFile = new MetaFile(key);
429: File refFile = new File(wsc.getRefCopyPath(), key);
430: File localFile = new File(wsc.getPath(), key);
431:
432: if (localFile.exists() || refFile.exists()) {
433: //metaFile.setFileType(wsc.getRefCopy().getDBType().getType(key));
434: metaFile.setFileType(wsc.getDBType().getType(
435: key));
436:
437: if (conflictFiles.contains(key)) {
438: metaFile.setConflict(true);
439: }
440:
441: if (refFile.exists()) {
442: // check if update
443: if (localFile.exists()) {
444: // maybe an update
445: if (new File(wsc.getPath(), key)
446: .isFile()
447: && !(FileUtils
448: .compareBinFile(
449: new File(
450: wsc
451: .getPath(),
452: key),
453: new File(
454: wsc
455: .getRefCopyPath(),
456: key)) || FileUtils
457: .compareTxtFile(
458: new File(
459: wsc
460: .getPath(),
461: key)
462: .getPath(),
463: new File(
464: wsc
465: .getRefCopyPath(),
466: key)
467: .getPath()))) {
468: metaFile
469: .setChangeType(MetaFile.CHANGE_TYPE_UPDATED_FILE);
470: }
471: } else {
472: // remove
473: metaFile
474: .setChangeType(MetaFile.CHANGE_TYPE_REMOVED_FILE);
475: }
476: } else {
477: // new file
478: metaFile
479: .setChangeType(MetaFile.CHANGE_TYPE_NEW_FILE);
480: }
481:
482: insertMetaFileInTree(metaFile);
483: }
484: }
485: }
486:
487: this .reload();
488: }
489:
490: private void insertMetaFileInTree(MetaFile metaFile) {
491: DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeNodeIndex
492: .get(metaFile.getPath().replaceAll("\\\\", "/"));
493:
494: if (node == null) {
495: // The node does not exist yet
496: insertPathInTree(metaFile.getPath());
497: node = (DefaultMutableTreeNode) treeNodeIndex
498: .get(metaFile.getPath().replaceAll("\\\\", "/"));
499: }
500:
501: MetaFile metaFileNode = (MetaFile) node.getUserObject();
502: metaFileNode.update(metaFile);
503: }
504:
505: private void insertPathInTree(String obj) {
506: String path = obj.replaceAll("\\\\", "/");
507: File f = new File(path);
508:
509: if ((f.getParent() == null) || path.equals(basePath)) {
510: DefaultMutableTreeNode child = new DefaultMutableTreeNode();
511: child.setUserObject(new MetaFile(f.getPath()));
512:
513: DefaultMutableTreeNode root = (DefaultMutableTreeNode) this
514: .getRoot();
515: this .insertNodeInto(child, root, 0);
516: treeNodeIndex.put(path, child);
517: this .reload(root);
518: } else {
519: DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeNodeIndex
520: .get(f.getParent().replaceAll("\\\\", "/"));
521:
522: if (node == null) {
523: insertPathInTree(f.getParent());
524: }
525:
526: node = (DefaultMutableTreeNode) treeNodeIndex.get(f
527: .getParent().replaceAll("\\\\", "/"));
528:
529: if (node == null) {
530: System.out
531: .println("Le parent est toujours null dans le add path");
532: } else {
533: DefaultMutableTreeNode child = new DefaultMutableTreeNode();
534: child.setUserObject(new MetaFile(f.getPath()));
535: this .insertNodeInto(child, node, 0);
536: this .reload(node);
537: treeNodeIndex.put(path, child);
538: }
539: }
540: }
541: }
542:
543: public class MetaFileTreeRenderer implements TreeCellRenderer {
544: public Component getTreeCellRendererComponent(JTree tree,
545: Object value, boolean selected, boolean expanded,
546: boolean leaf, int row, boolean hasFocus) {
547: JPanel panel = new JPanel();
548: panel.setBackground(Color.white);
549:
550: BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
551: panel.setLayout(layout);
552:
553: JLabel label = new JLabel();
554: label.setOpaque(true);
555:
556: DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
557: label.setBackground(selected ? Color.decode("#aaaaff")
558: : Color.decode("#ffffff"));
559:
560: if (treeNode.getUserObject() instanceof MetaFile) {
561: MetaFile metaFile = (MetaFile) treeNode.getUserObject();
562:
563: switch (metaFile.getFileType()) {
564: case MetaFile.FILE_TYPE_DIRECTORY:
565:
566: //ImageIcon icon =
567: // createImageIcon("images/middle.gif");
568: panel.add((createImageIcon("folder")));
569:
570: break;
571:
572: case MetaFile.FILE_TYPE_BIN:
573: panel.add((createImageIcon("binFile")));
574:
575: break;
576:
577: case MetaFile.FILE_TYPE_TXT:
578: panel.add((createImageIcon("txtFile")));
579:
580: break;
581:
582: case MetaFile.FILE_TYPE_XML:
583: panel.add((createImageIcon("xmlFile")));
584:
585: break;
586: }
587:
588: if (metaFile.isConflict()) {
589: panel.add((createImageIcon("conflict")));
590: }
591:
592: switch (metaFile.getChangeType()) {
593: case MetaFile.CHANGE_TYPE_NEW_FILE:
594: panel.add((createImageIcon("add")));
595:
596: break;
597:
598: case MetaFile.CHANGE_TYPE_REMOVED_FILE:
599: panel.add((createImageIcon("delete")));
600:
601: break;
602:
603: case MetaFile.CHANGE_TYPE_UPDATED_FILE:
604: panel.add((createImageIcon("update")));
605:
606: break;
607: }
608:
609: label.setText(metaFile.getName());
610: } else {
611: label.setText("Error");
612: }
613:
614: panel.add(label);
615: panel.invalidate();
616: panel.repaint();
617:
618: return panel;
619: }
620:
621: protected JLabel createImageIcon(String path) {
622: try {
623: InputStream in = getClass().getResourceAsStream(
624: "/org/libresource/so6/application/studio/resource/"
625: + path + ".gif");
626: byte[] buffer = new byte[in.available()];
627:
628: for (int i = 0, n = in.available(); i < n; i++)
629: buffer[i] = (byte) in.read();
630:
631: Toolkit toolkit = Toolkit.getDefaultToolkit();
632: Image image = toolkit.createImage(buffer);
633:
634: return new JLabel(new ImageIcon(image));
635: } catch (IOException e) {
636: return new JLabel("?");
637: }
638: }
639: }
640:
641: public class DefaultFileTreeActionListener implements
642: FileTreeActionListener {
643: public void editConflictOnFile(String filePath)
644: throws Exception {
645: ConflictEditor ce = new ConflictEditor(filePath);
646: JFrame f = new JFrame("Conflict editor: " + filePath);
647: f.getContentPane().add(ce, BorderLayout.CENTER);
648: f.setSize(400, 400);
649: f.setVisible(true);
650: }
651:
652: public void showFileHistory(WsConnection wsc, String filePath)
653: throws Exception {
654: FileHistoryViewer fhv = new FileHistoryViewer(wsc
655: .getDataPath()
656: + File.separator + WsConnection.SO6_WSC_FILE,
657: filePath);
658: JFrame f = new JFrame("File history: " + filePath);
659: f.getContentPane().add(fhv, BorderLayout.CENTER);
660: f.setSize(400, 400);
661: f.setVisible(true);
662: }
663:
664: public void rename(WsConnection wsc, String filePath)
665: throws Exception {
666: Rename mv = new Rename(wsc.getDataPath() + File.separator
667: + WsConnection.SO6_WSC_FILE, filePath, "");
668: JFrame f = new JFrame("Rename file on connection: "
669: + wsc.getDataPath());
670: f.getContentPane().add(mv, BorderLayout.CENTER);
671: f.setSize(400, 400);
672: f.setVisible(true);
673: }
674:
675: public void diff(WsConnection wsc, String filePath)
676: throws Exception {
677: String refFile = wsc.getRefCopyPath() + File.separator
678: + filePath;
679: String localFile = wsc.getPath() + File.separator
680: + filePath;
681: VDiff.main(new String[] { refFile, localFile });
682: }
683: }
684: }
|