diff --git a/src/main/java/eu/toldi/rss/Article.java b/src/main/java/eu/toldi/rss/Article.java index 4dfbf91..5c3e2e5 100644 --- a/src/main/java/eu/toldi/rss/Article.java +++ b/src/main/java/eu/toldi/rss/Article.java @@ -115,6 +115,7 @@ public class Article { this.pubDate = pubDate; } + public void open(){ Desktop desktop; if(Desktop.isDesktopSupported() && (desktop = Desktop.getDesktop()).isSupported(Desktop.Action.BROWSE)){ @@ -132,5 +133,4 @@ public class Article { } } } - } diff --git a/src/main/java/eu/toldi/rss/Feed.java b/src/main/java/eu/toldi/rss/Feed.java index 9e77418..56fefe3 100644 --- a/src/main/java/eu/toldi/rss/Feed.java +++ b/src/main/java/eu/toldi/rss/Feed.java @@ -71,11 +71,6 @@ public class Feed { } - @Override - public String toString() { - return name; - } - public void setName(String name) { this.name = name; } @@ -91,4 +86,13 @@ public class Feed { public String getLink() { return link.toString(); } + + public List
getArticleList() { + return articleList; + } + + @Override + public String toString() { + return name; + } } diff --git a/src/main/java/eu/toldi/rss/FeedData.java b/src/main/java/eu/toldi/rss/FeedData.java index 6b5c34d..8853be8 100644 --- a/src/main/java/eu/toldi/rss/FeedData.java +++ b/src/main/java/eu/toldi/rss/FeedData.java @@ -6,6 +6,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; + public class FeedData extends AbstractTableModel { private List feeds = new ArrayList(); @@ -96,6 +97,28 @@ public class FeedData extends AbstractTableModel { return feeds; } + public void removeFeed(Feed feed){ + boolean found = false; + for (int i = 0; i < feeds.size(); i++) { + //Csak ha PONTOSAN megegyezik + if(feeds.get(i) == feed){ + feeds.remove(i); + found =true; + break; + } + } + if(found){ + List
similar = new ArrayList
( articleList ); + List
different = new ArrayList
(); + different.addAll( articleList ); + different.addAll( feed.getArticleList() ); + + similar.retainAll( feed.getArticleList() ); + different.removeAll( similar ); + articleList = different; + } + } + @Override public String toString() { return "FeedData{" + diff --git a/src/main/java/eu/toldi/rss/FeedFrame.java b/src/main/java/eu/toldi/rss/FeedFrame.java index 94655e7..6a942d5 100644 --- a/src/main/java/eu/toldi/rss/FeedFrame.java +++ b/src/main/java/eu/toldi/rss/FeedFrame.java @@ -5,13 +5,21 @@ import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.table.TableRowSorter; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreePath; +import javax.swing.tree.*; import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.*; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +//TODO: Hírcsatornák rendezése +//TODO: Keresés cikkek között +//TODO: Automatikus frissítés public class FeedFrame extends JFrame { @@ -22,6 +30,7 @@ public class FeedFrame extends JFrame { private JMenuBar menubar; private JTree feeds; private TableRowSorter sorter; + private DefaultMutableTreeNode treeRoot; public FeedFrame() { @@ -34,22 +43,10 @@ public class FeedFrame extends JFrame { private void initData() { FeedLoader loader = new FeedLoader(); - //data = loader.loadFeedData("feeds.xml"); - data = new FeedData(); - FeedGroup group = new FeedGroup("Magyar hírek"); - try { - group.addFeed(new Feed(new URL("https://24.hu/feed"))); - group.addFeed(new Feed(new URL("https://444.hu/feed"))); - group.addFeed(new Feed(new URL("https://hvg.hu/rss"))); - data.addFeed(new Feed(new URL("https://www.theguardian.com/uk/rss"))); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - data.addFeed(group); + data = loader.loadFeedData("feeds.xml"); } - private void initTable(){ + private void initTable() { table = new JTable(); scrollPane = new JScrollPane(table); table.setModel(data); @@ -70,7 +67,7 @@ public class FeedFrame extends JFrame { } - private void initMenuBar(){ + private void initMenuBar() { menubar = new JMenuBar(); JMenu menu1 = new JMenu("File"); JMenuItem addFeed = new JMenuItem("Add Feed"); @@ -84,12 +81,33 @@ public class FeedFrame extends JFrame { this.setJMenuBar(menubar); } - private void initTree(){ - DefaultMutableTreeNode root = data.getRootNode(); - feeds = new JTree(root); + private void expandTree(JTree tree) { + DefaultMutableTreeNode root = + (DefaultMutableTreeNode) tree.getModel().getRoot(); + Enumeration e = root.breadthFirstEnumeration(); + while (e.hasMoreElements()) { + DefaultMutableTreeNode node = + (DefaultMutableTreeNode) e.nextElement(); + if (node.isLeaf()) continue; + int row = tree.getRowForPath(new TreePath(node.getPath())); + tree.expandRow(row); + } + } + + + private void initTree() { + treeRoot = data.getRootNode(); + feeds = new JTree(treeRoot); feeds.addMouseListener(new SwitchFeed()); - feeds.setEditable(true); - add(feeds,BorderLayout.WEST); + feeds.setEditable(false); + feeds.setDragEnabled(true); + feeds.setDropMode(DropMode.ON_OR_INSERT); + feeds.setTransferHandler(new TreeTransferHandler()); + feeds.getSelectionModel().setSelectionMode( + TreeSelectionModel.CONTIGUOUS_TREE_SELECTION); + expandTree(feeds); + add(feeds, BorderLayout.WEST); + } private void initComponents() { @@ -101,6 +119,12 @@ public class FeedFrame extends JFrame { initTree(); } + private void updateTeble(){ + sorter.setModel(data); + table.setRowSorter(sorter); + table.updateUI(); + } + private class AddFeed implements ActionListener { @Override @@ -118,8 +142,10 @@ public class FeedFrame extends JFrame { } catch (MalformedURLException malformedURLException) { malformedURLException.printStackTrace(); } + treeRoot.add(f.getTreeNode()); data.addFeed(f); table.updateUI(); + feeds.updateUI(); } else { System.out.println("Cancelled"); } @@ -141,19 +167,261 @@ public class FeedFrame extends JFrame { public void mousePressed(MouseEvent e) { int selRow = feeds.getRowForLocation(e.getX(), e.getY()); TreePath selPath = feeds.getPathForLocation(e.getX(), e.getY()); - if(selRow != -1) { - if(e.getClickCount() == 1) { - if(selRow == 0) data.limitTo(null); + if (selRow != -1) { + if (e.getClickCount() == 2) { + if (selRow == 0) data.limitTo(null); else { data.limitTo((Feed) ((DefaultMutableTreeNode) selPath.getLastPathComponent()).getUserObject()); } - sorter.setModel(data); - table.setRowSorter(sorter); - table.updateUI(); + updateTeble(); } } } } -} + class TreeTransferHandler extends TransferHandler { + DataFlavor nodesFlavor; + DataFlavor[] flavors = new DataFlavor[1]; + List nodesToRemove; + + public TreeTransferHandler() { + try { + String mimeType = DataFlavor.javaJVMLocalObjectMimeType + + ";class=\"" + + DefaultMutableTreeNode[].class.getName() + + "\""; + nodesFlavor = new DataFlavor(mimeType); + flavors[0] = nodesFlavor; + } catch (ClassNotFoundException e) { + System.out.println("ClassNotFound: " + e.getMessage()); + } + } + + public boolean canImport(TransferHandler.TransferSupport support) { + if (!support.isDrop() || support.getDropAction() == COPY) { + return false; + } + support.setShowDropLocation(true); + if (!support.isDataFlavorSupported(nodesFlavor)) { + return false; + } + // Do not allow a drop on the drag source selections. + JTree.DropLocation dl = + (JTree.DropLocation) support.getDropLocation(); + JTree tree = (JTree) support.getComponent(); + int dropRow = tree.getRowForPath(dl.getPath()); + int[] selRows = tree.getSelectionRows(); + for (int i = 0; i < selRows.length; i++) { + if (selRows[i] == dropRow) { + return false; + } + } + // Do not allow MOVE-action drops if a non-leaf node is + // selected unless all of its children are also selected. + int action = support.getDropAction(); + if (action == MOVE) { + return haveCompleteNode(tree); + } + // Do not allow a non-leaf node to be copied to a level + // which is less than its source level. + TreePath dest = dl.getPath(); + DefaultMutableTreeNode target = + (DefaultMutableTreeNode) dest.getLastPathComponent(); + TreePath path = tree.getPathForRow(selRows[0]); + DefaultMutableTreeNode firstNode = + (DefaultMutableTreeNode) path.getLastPathComponent(); + if (firstNode.getChildCount() > 0 && + target.getLevel() < firstNode.getLevel()) { + return false; + } + return true; + } + + private boolean haveCompleteNode(JTree tree) { + int[] selRows = tree.getSelectionRows(); + TreePath path = tree.getPathForRow(selRows[0]); + DefaultMutableTreeNode first = + (DefaultMutableTreeNode) path.getLastPathComponent(); + int childCount = first.getChildCount(); + // first has children and no children are selected. + if (childCount > 0 && selRows.length == 1) + return false; + // first may have children. + for (int i = 1; i < selRows.length; i++) { + path = tree.getPathForRow(selRows[i]); + DefaultMutableTreeNode next = + (DefaultMutableTreeNode) path.getLastPathComponent(); + if (first.isNodeChild(next)) { + // Found a child of first. + if (childCount > selRows.length - 1) { + // Not all children of first are selected. + return false; + } + } + } + return true; + } + + protected Transferable createTransferable(JComponent c) { + JTree tree = (JTree) c; + TreePath[] paths = tree.getSelectionPaths(); + if (paths != null) { + // Make up a node array of copies for transfer and + // another for/of the nodes that will be removed in + // exportDone after a successful drop. + List copies = new ArrayList(); + nodesToRemove = new ArrayList(); + DefaultMutableTreeNode node = (DefaultMutableTreeNode) paths[0].getLastPathComponent(); + DefaultMutableTreeNode copy = copy(node); + copies.add(copy); + nodesToRemove.add(node); + for (int i = 1; i < paths.length; i++) { + DefaultMutableTreeNode next = + (DefaultMutableTreeNode) paths[i].getLastPathComponent(); + // Do not allow higher level nodes to be added to list. + if (next.getLevel() < node.getLevel()) { + break; + } else if (next.getLevel() > node.getLevel()) { // child node + copy.add(copy(next)); + // node already contains child + } else { // sibling + copies.add(copy(next)); + nodesToRemove.add(next); + } + } + DefaultMutableTreeNode[] nodes = + copies.toArray(new DefaultMutableTreeNode[copies.size()]); + return new NodesTransferable(nodes); + } + return null; + } + + /** + * Defensive copy used in createTransferable. + */ + private DefaultMutableTreeNode copy(DefaultMutableTreeNode node) { + return new DefaultMutableTreeNode(node.getUserObject()); + } + + protected void exportDone(JComponent source, Transferable data, int action) { + if ((action & MOVE) == MOVE) { + JTree tree = (JTree) source; + DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); + // Remove nodes saved in nodesToRemove in createTransferable. + for (int i = 0; i < nodesToRemove.size(); i++) { + model.removeNodeFromParent(nodesToRemove.get(i)); + } + } + updateTeble(); + } + + public int getSourceActions(JComponent c) { + return COPY_OR_MOVE; + } + + public List getParents(DefaultMutableTreeNode[] nodes, TreePath[] paths) { + ArrayList results = new ArrayList(); + for (int i = 0; i < nodes.length; i++) { + for (int j = 0; j < paths.length; j++) { + if (nodes[i].getUserObject().equals(((DefaultMutableTreeNode)paths[j].getLastPathComponent()).getUserObject())) { + results.add((DefaultMutableTreeNode) ((DefaultMutableTreeNode) paths[j].getLastPathComponent()).getParent()); + } + } + } + return results; + } + + public boolean importData(TransferHandler.TransferSupport support) { + if (!canImport(support)) { + return false; + } + DefaultMutableTreeNode[] nodes = null; + try { + Transferable t = support.getTransferable(); + nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor); + } catch (UnsupportedFlavorException ufe) { + System.out.println("UnsupportedFlavor: " + ufe.getMessage()); + } catch (java.io.IOException ioe) { + System.out.println("I/O error: " + ioe.getMessage()); + } + JTree.DropLocation dl = + (JTree.DropLocation) support.getDropLocation(); + int childIndex = dl.getChildIndex(); + TreePath dest = dl.getPath(); + DefaultMutableTreeNode parent = + (DefaultMutableTreeNode) dest.getLastPathComponent(); + JTree tree = (JTree) support.getComponent(); + DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); + List parents = getParents(nodes,tree.getSelectionPaths()); + + int index = childIndex; // DropMode.INSERT + if (childIndex == -1) { // DropMode.ON + if (parent.getLevel() > 1) { + return false; + } + if (parent.isLeaf()) { + FeedGroup fg = new FeedGroup("New Feed Group"); + fg.addFeed((Feed) parent.getUserObject()); + DefaultMutableTreeNode newParent = fg.getTreeNode(); + data.removeFeed((Feed) parent.getUserObject()); + model.insertNodeInto(newParent, (MutableTreeNode) parent.getParent(), parent.getParent().getChildCount()); + for (int i = 0; i < nodes.length; i++) { + fg.addFeed((Feed) nodes[i].getUserObject()); + } + data.addFeed(fg); + model.removeNodeFromParent(parent); + parent = newParent; + } + + index = parent.getChildCount(); + } + for (int i = 0; i < nodes.length; i++) { + model.insertNodeInto(nodes[i], parent, index++); + if (parent != parents.get(i)) { + if (!(parents.get(i).isRoot())) { + FeedGroup fg = (FeedGroup) parents.get(i).getUserObject(); + fg.removeFeed((Feed) nodes[i].getUserObject()); + } else { + data.removeFeed((Feed) nodes[i].getUserObject()); + } + if(parents.get(i).getChildCount() == 1){ + model.removeNodeFromParent(parents.get(i)); + data.limitTo(null); + data.removeFeed((Feed) parent.getUserObject()); + + } + } + } + + return true; + } + + public String toString() { + return getClass().getName(); + } + + public class NodesTransferable implements Transferable { + DefaultMutableTreeNode[] nodes; + + public NodesTransferable(DefaultMutableTreeNode[] nodes) { + this.nodes = nodes; + } + + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return nodes; + } + + public DataFlavor[] getTransferDataFlavors() { + return flavors; + } + + public boolean isDataFlavorSupported(DataFlavor flavor) { + return nodesFlavor.equals(flavor); + } + } + } +} diff --git a/src/main/java/eu/toldi/rss/FeedGroup.java b/src/main/java/eu/toldi/rss/FeedGroup.java index 7a90cda..4099cc4 100644 --- a/src/main/java/eu/toldi/rss/FeedGroup.java +++ b/src/main/java/eu/toldi/rss/FeedGroup.java @@ -20,10 +20,21 @@ public class FeedGroup extends Feed { } public void addFeed(Feed feed){ - feedList.add(feed); - for (int i = 0; i < feed.getArticleCount(); i++) { - articleList.add(feed.get(i)); + if(!feedExists(feed)) { + feedList.add(feed); + for (int i = 0; i < feed.getArticleCount(); i++) { + articleList.add(feed.get(i)); + } + }else + System.out.println("Feed exists"); + } + + public boolean feedExists(Feed f){ + for (int i = 0; i < feedList.size(); i++) { + if(f == feedList.get(i)) + return true; } + return false; } @Override @@ -55,4 +66,27 @@ public class FeedGroup extends Feed { } return folder; } + + public void removeFeed(Feed feed){ + boolean found = false; + for (int i = 0; i < feedList.size(); i++) { + //Csak ha PONTOSAN megegyezik + if(feedList.get(i) == feed){ + feedList.remove(i); + found =true; + break; + } + } + if(found){ + List
similar = new ArrayList
( articleList ); + List
different = new ArrayList
(); + different.addAll( articleList ); + different.addAll( feed.getArticleList() ); + + similar.retainAll( feed.getArticleList() ); + different.removeAll( similar ); + articleList = different; + } + } + } diff --git a/src/main/java/eu/toldi/rss/FeedLoader.java b/src/main/java/eu/toldi/rss/FeedLoader.java index a2775a4..9d2e356 100644 --- a/src/main/java/eu/toldi/rss/FeedLoader.java +++ b/src/main/java/eu/toldi/rss/FeedLoader.java @@ -9,10 +9,8 @@ import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; +import java.net.MalformedURLException; import java.net.URL; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -22,6 +20,38 @@ public class FeedLoader { private FeedData data; + + private Feed getFeed(NodeList feedProperties) throws MalformedURLException { + List
articleList = new ArrayList
(); + String url = null; + for (int j = 1; j < feedProperties.getLength(); j+=2) { + Node node = feedProperties.item(j); + String tagName = node.getNodeName(); + + + switch (tagName){ + case "link": + url = node.getTextContent(); + break; + case "articleList": + NodeList articles = node.getChildNodes(); + for (int k = 0; k < articles.getLength(); k++) { + Node article = articles.item(k); + Article a = new Article(article, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + if(a.getURL() != null) + articleList.add(a); + } + } + if(url == null) return null; + } + Feed feed = new Feed(new URL(url)); + System.out.println(articleList.size()); + for (int k = 0; k < articleList.size(); k++) { + feed.addArticle(articleList.get(k)); + } + return feed; + } + public FeedData loadFeedData(String filename) { DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance(); data = new FeedData(); @@ -29,40 +59,38 @@ public class FeedLoader { try { DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File(filename)); - NodeList feeds = doc.getElementsByTagName("feed"); - for (int i = 0; i < feeds.getLength(); i++) { - NodeList feedProperties = feeds.item(i).getChildNodes(); - List
articleList = new ArrayList
(); - String url = null; - for (int j = 0; j < feedProperties.getLength(); j++) { - Node node = feedProperties.item(j); - String tagName = node.getNodeName(); + NodeList feeds = doc.getFirstChild().getChildNodes(); + for (int i = 1; i < feeds.getLength(); i=i+2) { + if(feeds.item(i).getNodeName().equals("feed")) { + Feed feed = getFeed(feeds.item(i).getChildNodes()); + if(feed != null)data.addFeed(feed); + }else if (feeds.item(i).getNodeName().equals("feedGroup")){ + NodeList feedGroupprops = feeds.item(i).getChildNodes(); + String name = null; + FeedGroup fg = null; + for (int j = 1; j < feedGroupprops.getLength(); j+=2) { + Node node = feedGroupprops.item(j); + String tagName = node.getNodeName(); - - switch (tagName){ - case "link": - url = node.getTextContent(); - break; - case "articleList": - NodeList articles = node.getChildNodes(); - for (int k = 0; k < articles.getLength(); k++) { - Node article = articles.item(k); - Article a = new Article(article, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - if(a.getURL() != null) - articleList.add(a); - } + switch (tagName){ + case "name": + fg = new FeedGroup(node.getTextContent()); + break; + case "feedList": + if(fg == null) continue; + NodeList feedList1 = node.getChildNodes(); + for (int k = 1; k < feedList1.getLength(); k+=2) { + Node f = feedList1.item(k); + Feed feed = getFeed(f.getChildNodes()); + fg.addFeed(feed); + } + } } - if(url == null) continue; - } - Feed feed = new Feed(new URL(url)); - System.out.println(articleList.size()); - for (int k = 0; k < articleList.size(); k++) { - System.out.println(); - feed.addArticle(articleList.get(k)); - } - - data.addFeed(feed); + if(fg != null) + data.addFeed(fg); + }else + System.out.println(feeds.item(i).getNodeName()); } } catch (ParserConfigurationException e) { e.printStackTrace(); @@ -71,16 +99,19 @@ public class FeedLoader { } catch (SAXException e) { e.printStackTrace(); } + return data; } + + public void saveFeedData() { XStream xStream = new XStream(); xStream.processAnnotations(Article.class); xStream.processAnnotations(Feed.class); xStream.processAnnotations(FeedGroup.class); - xStream.omitField(FeedData.class,"articleList"); - xStream.omitField(javax.swing.event.EventListenerList.class,"listenerList"); + xStream.omitField(FeedData.class, "articleList"); + xStream.omitField(javax.swing.event.EventListenerList.class, "listenerList"); String xml = xStream.toXML(data.getFeeds()); BufferedWriter writer; try {