2013-01-23 17 views
5

Mam JTree w JScrollPane. Model JTree jest dość długi, więc przeciągnięcie węzła z góry drzewa na dół zajmuje trochę czasu. Podczas przeciągania węzła przesuwa się JScrollPane, ale nie tak szybko, jak przewija za pomocą kółka myszy. Implementacja setUnitIncrement zgodnie z sugestią pod wybraną odpowiedzią powoduje, że szybkość przewijania myszy jest jeszcze szybsza, ale nie zmienia prędkości przeciągania węzła. To samo dotyczy implementacji setBlockIncrement. Szybkość przewijania podczas przeciągania węzła jest mniej więcej taka sama, jak w przypadku trzymania strzałki w górę lub w dół i przechodzenia przez nią w ten sposób.Jak przyspieszyć przewijanie podczas przeciągania węzła JTree wewnątrz JScrollPane

Jak mogę przyspieszyć prędkość przeciągania węzła?

UPDATE 1:

Oto SSCCE na żądanie. Większość tego kodu zerwałem z here, ponieważ dobrze ilustruje problem, który mam. Po prostu przeciągnij węzeł do części drzewa, która nie jest widoczna w panelu przewijania, a zobaczysz, jak wolne jest przewijanie. To właśnie chcę przyspieszyć.

package example; 

import java.awt.datatransfer.*; 
import java.util.*; 
import javax.swing.*; 
import javax.swing.tree.*; 

public class Example { 

    private JScrollPane getContent() { 
     ArrayList<String> arrayList = new ArrayList<String>(); 
     for(int i = 0; i < 3000; i++) { 
      arrayList.add(String.format("Node %d", i)); 
     } 
     DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root"); 
     for(String s : arrayList) { 
      root.add(new DefaultMutableTreeNode(s)); 
     } 
     JTree tree = new JTree(root); 
     tree.setDragEnabled(true); 
     tree.setDropMode(DropMode.ON_OR_INSERT); 
     tree.setTransferHandler(new TreeTransferHandler()); 
     tree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION); 
     expandTree(tree); 
     return new JScrollPane(tree); 
    } 

    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); 
     } 
    } 

    public static void main(String[] args) { 
     JFrame f = new JFrame(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.add(new Example().getContent()); 
     f.setSize(400, 400); 
     f.setLocation(200, 200); 
     f.setVisible(true); 
    } 
} 

class TreeTransferHandler extends TransferHandler { 

    DataFlavor nodesFlavor; 
    DataFlavor[] flavors = new DataFlavor[1]; 
    DefaultMutableTreeNode[] nodesToRemove; 

    public TreeTransferHandler() { 
     try { 
      String mimeType = DataFlavor.javaJVMLocalObjectMimeType 
        + ";class=\"" 
        + javax.swing.tree.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()) { 
      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<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>(); 
      List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>(); 
      DefaultMutableTreeNode node = (DefaultMutableTreeNode)paths[0].getLastPathComponent(); 
      DefaultMutableTreeNode copy = copy(node); 
      copies.add(copy); 
      toRemove.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)); 
        toRemove.add(next); 
       } 
      } 
      DefaultMutableTreeNode[] nodes = copies.toArray(new DefaultMutableTreeNode[copies.size()]); 
      nodesToRemove = toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]); 
      return new NodesTransferable(nodes); 
     } 
     return null; 
    } 

    /** 
    * Defensive copy used in createTransferable. 
    */ 
    private DefaultMutableTreeNode copy(TreeNode node) { 
     return new DefaultMutableTreeNode(node); 
    } 

    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.length; i++) { 
       model.removeNodeFromParent(nodesToRemove[i]); 
      } 
     } 
    } 

    public int getSourceActions(JComponent c) { 
     return COPY_OR_MOVE; 
    } 

    public boolean importData(TransferHandler.TransferSupport support) { 
     if(!canImport(support)) { 
      return false; 
     } 
     // Extract transfer data. 
     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()); 
     } 
     // Get drop location info. 
     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(); 
     // Configure for drop mode. 
     int index = childIndex; // DropMode.INSERT 
     if(childIndex == -1) {  // DropMode.ON 
      index = parent.getChildCount(); 
     } 
     // Add data to model. 
     for(int i = 0; i < nodes.length; i++) { 
      model.insertNodeInto(nodes[i], parent, index++); 
     } 
     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); 
     } 
    } 
} 

SUKCES!

Byłem w stanie w końcu przyspieszyć przewijanie podczas przeciągania. W rzeczywistości byłem również w stanie ustawić zmienną prędkość przewijania na podstawie tego, jak blisko jest kursor do górnej lub dolnej części drzewa. W końcu okazało się, że jest o wiele prostsze, niż ja to robiłem. Nawet nie potrzebował słuchacza. Po prostu rozwiń te dwa przykłady kodu, aby zobaczyć, co dodałem.

package example; 

import java.awt.Point; 
import java.awt.Rectangle; 
import java.awt.datatransfer.*; 
import java.util.*; 
import javax.swing.*; 
import javax.swing.tree.*; 

public class Example { 

    private JScrollPane getContent() { 
     ArrayList<String> arrayList = new ArrayList<String>(); 
     for(int i = 0; i < 3000; i++) { 
      arrayList.add(String.format("Node %d", i)); 
     } 
     DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root"); 
     for(String s : arrayList) { 
      root.add(new DefaultMutableTreeNode(s)); 
     } 
     JTree tree = new JTree(root); 
     tree.setDragEnabled(true); 
     tree.setDropMode(DropMode.ON_OR_INSERT); 
     tree.setTransferHandler(new TreeTransferHandler()); 
     tree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION); 
     expandTree(tree); 
     return new JScrollPane(tree); 
    } 

    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); 
     } 
    } 

    public static void main(String[] args) { 
     JFrame f = new JFrame(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.add(new Example().getContent()); 
     f.setSize(400, 400); 
     f.setLocation(200, 200); 
     f.setVisible(true); 
    } 
} 

class TreeTransferHandler extends TransferHandler { 

    DataFlavor nodesFlavor; 
    DataFlavor[] flavors = new DataFlavor[1]; 
    DefaultMutableTreeNode[] nodesToRemove; 

    public TreeTransferHandler() { 
     try { 
      String mimeType = DataFlavor.javaJVMLocalObjectMimeType 
        + ";class=\"" 
        + javax.swing.tree.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()) { 
      return false; 
     } 

     boolean isScrolling = false; 
     JTree tree = (JTree)support.getComponent(); 
     JViewport vp = (JViewport)tree.getParent(); 
     Point vpMousePosition = vp.getMousePosition(); 
     Rectangle treeVisibleRectangle = tree.getVisibleRect(); 

     // Don't attempt scroll if mouse isn't over tree 
     if(vpMousePosition != null) { 
      Integer newY = null; 

      // Make sure we aren't already scrolled all the way down 
      if(tree.getHeight() - treeVisibleRectangle.y != vp.getHeight()) { 
       /* 
       * Get Y coordinate for scrolling down 
       */ 
       if(vp.getHeight() - vpMousePosition.y < 10) { 
        newY = treeVisibleRectangle.y + 500; 
       } else if(vp.getHeight() - vpMousePosition.y < 20) { 
        newY = treeVisibleRectangle.y + 400; 
       } else if(vp.getHeight() - vpMousePosition.y < 30) { 
        newY = treeVisibleRectangle.y + 300; 
       } else if(vp.getHeight() - vpMousePosition.y < 40) { 
        newY = treeVisibleRectangle.y + 200; 
       } else if(vp.getHeight() - vpMousePosition.y < 50) { 
        newY = treeVisibleRectangle.y + 100; 
       } else if(vp.getHeight() - vpMousePosition.y < 60) { 
        newY = treeVisibleRectangle.y + 50; 
       } else if(vp.getHeight() - vpMousePosition.y < 70) { 
        newY = treeVisibleRectangle.y + 25; 
       } else if(vp.getHeight() - vpMousePosition.y < 80) { 
        newY = treeVisibleRectangle.y + 10; 
       } 
      } 

      // Make sure we aren't already scrolled all the way up 
      if(newY == null && treeVisibleRectangle.y != 0) { 
       /* 
       * Get Y coordinate for scrolling up 
       */ 
       if(10 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 500; 
       } else if(20 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 400; 
       } else if(30 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 300; 
       } else if(40 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 200; 
       } else if(50 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 100; 
       } else if(60 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 50; 
       } else if(70 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 25; 
       } else if(80 > vpMousePosition.y) { 
        newY = treeVisibleRectangle.y - 10; 
       } 
      } 

      // Do the scroll 
      if(newY != null ) { 
       Rectangle treeNewVisibleRectangle = new Rectangle(treeVisibleRectangle.x, newY, treeVisibleRectangle.width, treeVisibleRectangle.height); 
       tree.scrollRectToVisible(treeNewVisibleRectangle); 
       isScrolling = true; 
      } 
     } 

     if(isScrolling) { 
      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(); 
     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<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>(); 
      List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>(); 
      DefaultMutableTreeNode node = (DefaultMutableTreeNode)paths[0].getLastPathComponent(); 
      DefaultMutableTreeNode copy = copy(node); 
      copies.add(copy); 
      toRemove.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)); 
        toRemove.add(next); 
       } 
      } 
      DefaultMutableTreeNode[] nodes = copies.toArray(new DefaultMutableTreeNode[copies.size()]); 
      nodesToRemove = toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]); 
      return new NodesTransferable(nodes); 
     } 
     return null; 
    } 

    /** 
    * Defensive copy used in createTransferable. 
    */ 
    private DefaultMutableTreeNode copy(TreeNode node) { 
     return new DefaultMutableTreeNode(node); 
    } 

    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.length; i++) { 
       model.removeNodeFromParent(nodesToRemove[i]); 
      } 
     } 
    } 

    public int getSourceActions(JComponent c) { 
     return COPY_OR_MOVE; 
    } 

    public boolean importData(TransferHandler.TransferSupport support) { 
     if(!canImport(support)) { 
      return false; 
     } 
     // Extract transfer data. 
     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()); 
     } 
     // Get drop location info. 
     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(); 
     // Configure for drop mode. 
     int index = childIndex; // DropMode.INSERT 
     if(childIndex == -1) {  // DropMode.ON 
      index = parent.getChildCount(); 
     } 
     // Add data to model. 
     for(int i = 0; i < nodes.length; i++) { 
      model.insertNodeInto(nodes[i], parent, index++); 
     } 
     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); 
     } 
    } 
} 
+0

Edytuj swoje pytanie, dołączając [sscce] (http://sscce.org/), które wyjaśnia pojęcie _rather long_. – trashgod

+0

@trashgod dodał SSCCE zgodnie z żądaniem. – ubiquibacon

+0

+1 Wasz [sscce] (http://sscce.org/) wyjaśnia, że ​​przewijanie często się zacina, a nowy cel spadania automatycznie przewija się do widoku. Przepraszam, nie wiem, jak obejść. Czy można wyciąć i wkleić rozsądną alternatywę? – trashgod

Odpowiedz

2

Kiedy nie działa ładnie z SDK, niż ja rozpoczynam moje hackish metod, w tym przypadku jest to, że niby: słuchać myszy. Kiedy mysz przesunęła się w dół o 10 pikseli, wykonasz zdarzenie, gdy mysz przesunie się o 100 pikseli w tym samym kierunku. W systemie Windows, w programie Excel, jeśli przeciągniesz pod paskiem zadań, mysz wyląduje przewijanie. Coś podobnego powinno być tutaj, warto spróbować.

+1

Wziąłem twoją sugestię, ale nie byłem w stanie sprawić, by rzeczy działały poprawnie używając słuchaczy. Słuchacze myszy Java nie grają dobrze z metodami DnD. Na przykład nie mogłem wykryć zdarzenia typu "mouse up", gdy wykonywana była operacja DnD. Kod, który dodałem do mojego pytania, przewija drzewo w oparciu o lokalizację myszy, jak sugerowałeś, więc dam ci to do siebie :) – ubiquibacon

Powiązane problemy