/*
* Copyright 2016 Igor Maznitsa.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.igormaznitsa.sciareto.ui.tree;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.apache.commons.io.FilenameUtils;
import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.meta.common.utils.ArrayUtils;
import com.igormaznitsa.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.mindmap.model.nio.Path;
import com.igormaznitsa.mindmap.model.nio.Paths;
import com.igormaznitsa.sciareto.Context;
import com.igormaznitsa.sciareto.ui.DialogProviderManager;
import com.igormaznitsa.sciareto.ui.UiUtils;
public class NodeProjectGroup extends NodeFileOrFolder implements TreeModel {
protected final String groupName;
protected final List<TreeModelListener> listeners = new CopyOnWriteArrayList<>();
private final Context context;
public static final Pattern FILE_NAME = Pattern.compile("^[^\\+\\*\\?\\{\\}\\&\\|\\;\\:\\\\\\/]+$"); //NOI18N
private static final Logger LOGGER = LoggerFactory.getLogger(NodeProjectGroup.class);
public NodeProjectGroup(@Nonnull final Context context, @Nonnull final String name) {
super(null, true, ".", false); //NOI18N
this.groupName = name;
this.context = context;
}
@Override
@Nullable
public File makeFileForNode() {
return null;
}
@Nullable
public NodeProject findForFolder(@Nonnull final File folder) {
NodeProject result = null;
for (final NodeFileOrFolder n : this.children) {
if (folder.equals(((NodeProject) n).getFolder())) {
result = (NodeProject) n;
}
}
return result;
}
public void removeProject(@Nonnull final NodeProject project) {
int index = this.children.indexOf(project);
if (index >= 0 && this.children.remove(project)) {
final TreeModelEvent event = new TreeModelEvent(this, new Object[]{this}, new int[]{index}, new Object[]{project});
for (final TreeModelListener l : this.listeners) {
l.treeNodesRemoved(event);
}
}
}
@Nonnull
public NodeProject addProjectFolder(@Nonnull final File folder) {
NodeProject newProject = findForFolder(folder);
if (newProject == null) {
newProject = new NodeProject(this, folder);
final int index = this.children.size();
this.children.add(newProject);
final TreeModelEvent event = new TreeModelEvent(this, new Object[]{this}, new int[]{index}, new Object[]{newProject});
for (final TreeModelListener l : this.listeners) {
l.treeNodesInserted(event);
}
}
return newProject;
}
@Override
@Nonnull
public Object getRoot() {
return this;
}
@Override
@Nonnull
public Object getChild(@Nonnull final Object parent, final int index) {
return ((NodeFileOrFolder) parent).getChildAt(index);
}
@Override
public int getChildCount(@Nonnull final Object parent) {
return ((NodeFileOrFolder) parent).getChildCount();
}
@Override
public boolean isLeaf(@Nonnull final Object node) {
return ((NodeFileOrFolder) node).isLeaf();
}
@Override
public void valueForPathChanged(@Nonnull final TreePath path, @Nonnull final Object newValue) {
String newFileName = String.valueOf(newValue);
if (FILE_NAME.matcher(newFileName).matches()) {
final Object last = path.getLastPathComponent();
if (last instanceof NodeFileOrFolder) {
final NodeFileOrFolder editedNode = (NodeFileOrFolder) last;
final String oldName = editedNode.toString();
if (!oldName.equals(newFileName)) {
final File origFile = ((NodeFileOrFolder) last).makeFileForNode();
final String oldExtension = FilenameUtils.getExtension(oldName);
final String newExtension = FilenameUtils.getExtension(newFileName);
if (!oldExtension.equals(newExtension)){
if (DialogProviderManager.getInstance().getDialogProvider().msgConfirmYesNo("Changed extension", String.format("You have changed extension! Restore old extension '%s'?",oldExtension))){
newFileName = FilenameUtils.getBaseName(newFileName)+(oldExtension.isEmpty() ? "" : '.'+oldExtension); //NOI18N
}
}
if (origFile != null) {
final File newFile = new File(origFile.getParentFile(), newFileName);
if (!editedNode.isLeaf() && !context.safeCloseEditorsForFile(origFile)) {
return;
}
try {
boolean doIt = true;
List<File> affectedFiles = null;
final NodeProject project = editedNode.findProject();
if (project != null) {
affectedFiles = project.findAffectedFiles(origFile);
if (!affectedFiles.isEmpty()) {
affectedFiles = UiUtils.showSelectAffectedFiles(affectedFiles);
if (affectedFiles == null) {
doIt = false;
} else {
affectedFiles = project.replaceAllLinksToFile(affectedFiles, origFile, newFile);
}
}
}
if (doIt) {
Files.move(origFile.toPath(), newFile.toPath());
editedNode.setName(newFile.getName());
editedNode.fireNotifySubtreeChanged(this, listeners);
this.context.notifyFileRenamed(affectedFiles, origFile, newFile);
}
} catch (IOException ex) {
LOGGER.error("Can't rename file", ex); //NOI18N
DialogProviderManager.getInstance().getDialogProvider().msgError("Can't rename file to '" + newValue + "\'");
}
}
}
}
} else {
DialogProviderManager.getInstance().getDialogProvider().msgError("Inapropriate file name '" + newFileName + "'!");
}
}
@Override
public int getIndexOfChild(@Nonnull final Object parent, @Nonnull final Object child) {
return ((NodeFileOrFolder) parent).getIndex((NodeFileOrFolder) child);
}
@Override
public void addTreeModelListener(@Nonnull final TreeModelListener l) {
this.listeners.add(l);
}
@Override
public void removeTreeModelListener(@Nonnull final TreeModelListener l) {
this.listeners.remove(l);
}
@Nullable
public NodeProject findProjectForFile(@Nonnull final File file) {
final Path filepath = Paths.toPath(file);
for (final NodeFileOrFolder t : this.children) {
final File projectFolder = ((NodeProject) t).getFolder();
if (filepath.startsWith(Paths.toPath(projectFolder))) {
return (NodeProject) t;
}
}
return null;
}
@Override
@Nullable
public TreePath findPathToFile(@Nonnull final File file) {
TreePath path = null;
for (final NodeFileOrFolder p : this.children) {
path = p.findPathToFile(file);
if (path != null) {
break;
}
}
if (path != null) {
path = new TreePath(ArrayUtils.joinArrays(new Object[]{this}, path.getPath()));
}
return path;
}
public void refreshProjectFolder(@Nonnull final NodeProject nodeProject) {
final int index = this.getIndex(nodeProject);
if (index >= 0) {
nodeProject.reloadSubtree();
nodeProject.fireNotifySubtreeChanged(this, this.listeners);
}
}
public boolean fireNotificationThatNodeDeleted(@Nonnull final NodeFileOrFolder node) {
final NodeFileOrFolder parentNode = node.getNodeParent();
if (parentNode != null) {
final TreeModelEvent event = new TreeModelEvent(this, parentNode.makeTreePath(), new int[]{node.getIndexAtParent()}, new Object[]{node});
if (parentNode.deleteChild(node)) {
for (final TreeModelListener l : this.listeners) {
l.treeNodesRemoved(event);
}
return true;
}
}
return false;
}
public void addChild(@Nonnull final NodeFileOrFolder folder, @Nonnull final File childFile) {
final NodeFileOrFolder newNode = folder.addFile(childFile);
final TreeModelEvent event = new TreeModelEvent(this, folder.makeTreePath(), new int[]{newNode.getIndexAtParent()}, new Object[]{newNode});
for (final TreeModelListener l : this.listeners) {
l.treeNodesInserted(event);
}
}
@Nonnull
@MustNotContainNull
public List<NodeFileOrFolder> findForNamePattern(@Nullable final Pattern namePattern) {
final List<NodeFileOrFolder> result = new ArrayList<>();
if (namePattern != null) {
for (final NodeFileOrFolder f : this.children) {
f.fillAllMatchNamePattern(namePattern, result);
}
}
return result;
}
}