/*
* Nocturne
* Copyright (c) 2015-2016, Lapis <https://github.com/LapisBlue>
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package blue.lapis.nocturne.gui.scene.control;
import static blue.lapis.nocturne.util.Constants.CLASS_PATH_SEPARATOR_PATTERN;
import static blue.lapis.nocturne.util.Constants.Processing.CLASS_REGEX;
import static blue.lapis.nocturne.util.Constants.Processing.MEMBER_REGEX;
import blue.lapis.nocturne.Main;
import blue.lapis.nocturne.gui.scene.text.SelectableMember;
import blue.lapis.nocturne.util.JavaSyntaxHighlighter;
import blue.lapis.nocturne.util.MemberType;
import blue.lapis.nocturne.util.helper.StringHelper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The code-tab JavaFX component.
*/
public class CodeTab extends Tab {
public static final Map<String, CodeTab> CODE_TABS = Maps.newHashMap();
private final String className;
public Label memberIdentifierLabel;
public Label memberInfoLabel;
public Label memberIdentifier;
public Label memberInfo;
public TextFlow code;
public CodeTab(TabPane pane, String className, String displayName) {
this.className = className;
this.setText(CLASS_PATH_SEPARATOR_PATTERN.matcher(displayName).replaceAll("."));
pane.getTabs().add(this);
FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("fxml/CodeTab.fxml"));
loader.setResources(Main.getResourceBundle());
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
CODE_TABS.put(className, this);
getTabPane().getSelectionModel().select(this);
this.setOnClosed(event -> CODE_TABS.remove(this.getClassName()));
}
public String getClassName() {
return className;
}
public void resetClassName() {
this.setText(CLASS_PATH_SEPARATOR_PATTERN.matcher(className).replaceAll("."));
}
/**
* Sets the member type of the selected member.
*
* @param type the member type.
*/
public void setMemberType(SelectableMemberType type) {
this.memberIdentifierLabel.setText(String.format("%s: ", type.getIdentifierLabel()));
if (type.isInfoEnabled()) {
this.memberInfoLabel.setText(String.format("%s: ", type.getInfoLabel()));
this.memberInfo.setVisible(true);
this.memberInfoLabel.setVisible(true);
} else {
this.memberInfo.setVisible(false);
this.memberInfoLabel.setVisible(false);
}
}
/**
* Sets the member identifier of the selected member.
*
* @param identifier The member identifier.
*/
public void setMemberIdentifier(String identifier) {
this.memberIdentifier.setText(identifier);
}
/**
* Sets the member info of the selected member.
*
* @param info The member info.
*/
public void setMemberInfo(String info) {
this.memberInfo.setText(info);
}
/**
* Sets the open source file's code.
*
* @param code The code.
*/
public void setCode(String code) {
this.code.getChildren().clear();
List<Node> nodes = Lists.newArrayList(new Text(code));
parseItems(nodes, CLASS_REGEX, 1);
parseItems(nodes, MEMBER_REGEX, 2);
JavaSyntaxHighlighter.highlight(nodes);
nodes.forEach(node -> ((Text) node).setFont(Font.font("monospace", ((Text) node).getFont().getSize())));
Node[] nodeArr = new Node[nodes.size()];
nodes.toArray(nodeArr);
TextFlow flow = new TextFlow(nodeArr);
this.code.getChildren().add(flow);
}
public enum SelectableMemberType {
FIELD("codetab.identifier.field", "codetab.identifier.type"),
METHOD("codetab.identifier.method", "codetab.identifier.descriptor"),
ARG("codetab.identifier.param", "codetab.identifier.type"),
CLASS("codetab.identifier.class"),
;
private final String identifierLabel;
private final String infoLabel;
private final boolean infoEnabled;
SelectableMemberType(String identifierLabel, String infoLabel, boolean infoEnabled) {
this.identifierLabel = identifierLabel;
this.infoLabel = infoLabel;
this.infoEnabled = infoEnabled;
}
SelectableMemberType(String identifierLabel) {
this(identifierLabel, "", false);
}
SelectableMemberType(String identifierLabel, String infoLabel) {
this(identifierLabel, infoLabel, true);
}
/**
* Gets the localised identifier label for this member type.
*
* @return The identifier label.
*/
public String getIdentifierLabel() {
return Main.getResourceBundle().getString(this.identifierLabel);
}
/**
* Gets the localised info label for this member type.
*
* @return The info label.
*/
public String getInfoLabel() {
return Main.getResourceBundle().getString(this.infoLabel);
}
/**
* Gets if the info label should be displayed or not.
*
* @return {@code True} if the label should be displayed.
*/
public boolean isInfoEnabled() {
return infoEnabled;
}
public static SelectableMemberType fromMemberType(MemberType type) {
switch (type) {
case CLASS:
return SelectableMemberType.CLASS;
case FIELD:
return SelectableMemberType.FIELD;
case METHOD:
return SelectableMemberType.METHOD;
case ARG:
return SelectableMemberType.ARG;
default:
throw new AssertionError();
}
}
}
public void parseItems(List<Node> nodes, Pattern pattern, int defaultGroup) {
List<Node> newNodes = new ArrayList<>();
for (Node node : nodes) {
if (node.getClass() != Text.class) {
newNodes.add(node);
continue;
}
String str = ((Text) node).getText();
Matcher matcher = pattern.matcher(str);
int lastIndex = 0;
while (matcher.find()) {
newNodes.add(new Text(str.substring(lastIndex, matcher.start())));
SelectableMember sm = SelectableMember.fromMatcher(this, matcher);
if (sm != null) {
newNodes.add(sm);
} else {
newNodes.add(new Text(StringHelper.unqualify(matcher.group(defaultGroup))));
}
lastIndex = matcher.end();
}
newNodes.add(new Text(str.substring(lastIndex)));
}
nodes.clear();
nodes.addAll(newNodes);
}
}