Skip to content

Commit a2795c1

Browse files
committed
implement files tab pane
1 parent 8ec09d6 commit a2795c1

3 files changed

Lines changed: 315 additions & 31 deletions

File tree

src/main/java/gr/sqlbrowserfx/nodes/DBTreeView.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class DBTreeView extends TreeView<String>
7878
private final SimpleBooleanProperty canSelectedOpenProperty = new SimpleBooleanProperty(false);
7979
private HBox searchBox;
8080
private String currentSearchPattern;
81+
private SimpleBooleanProperty isLoadingProperty = new SimpleBooleanProperty(true);
8182

8283

8384

@@ -190,8 +191,8 @@ private void setupSelectionChangeListener() {
190191
@Override
191192
public void setInputMap() {
192193
// enable following line if this view used as standalone
193-
// Nodes.addInputMap(this, InputMap.consume(EventPattern.keyPressed(KeyCode.F, KeyCombination.CONTROL_DOWN),
194-
// action -> this.showSearchPopup()));
194+
Nodes.addInputMap(this, InputMap.consume(EventPattern.keyPressed(KeyCode.F, KeyCombination.CONTROL_DOWN),
195+
action -> this.showSearchPopup()));
195196
Nodes.addInputMap(this, InputMap.consume(EventPattern.keyPressed(KeyCode.C, KeyCombination.CONTROL_DOWN),
196197
action -> this.copyAction()));
197198
}
@@ -381,6 +382,7 @@ private void fillTreeView() throws SQLException {
381382
});
382383

383384
timeCounter = (System.currentTimeMillis() - timeCounter) / 1000;
385+
isLoadingProperty.set(false);
384386
LoggerFactory.getLogger(LoggerConf.LOGGER_NAME)
385387
.info("Database analysis took " + timeCounter + " seconds");
386388
this.changed();
@@ -820,4 +822,8 @@ public SimpleBooleanProperty hasSelectedSchemaProperty() {
820822
public SimpleBooleanProperty canSelectedOpenProperty() {
821823
return canSelectedOpenProperty;
822824
}
825+
826+
public SimpleBooleanProperty isLoadingProperty() {
827+
return isLoadingProperty;
828+
}
823829
}
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
package gr.sqlbrowserfx.nodes;
2+
3+
import java.awt.Toolkit;
4+
import java.awt.datatransfer.StringSelection;
5+
import java.io.File;
6+
import java.sql.SQLException;
7+
import java.util.concurrent.atomic.AtomicBoolean;
8+
import java.util.concurrent.atomic.AtomicLong;
9+
10+
import org.apache.commons.lang3.StringUtils;
11+
import org.fxmisc.flowless.VirtualizedScrollPane;
12+
import org.fxmisc.wellbehaved.event.EventPattern;
13+
import org.fxmisc.wellbehaved.event.InputMap;
14+
import org.fxmisc.wellbehaved.event.Nodes;
15+
import org.slf4j.LoggerFactory;
16+
17+
import gr.sqlbrowserfx.LoggerConf;
18+
import gr.sqlbrowserfx.conn.SqlConnector;
19+
import gr.sqlbrowserfx.factories.DialogFactory;
20+
import gr.sqlbrowserfx.nodes.codeareas.AutoCompleteCodeArea;
21+
import gr.sqlbrowserfx.nodes.codeareas.FileCodeArea;
22+
import gr.sqlbrowserfx.nodes.codeareas.SimpleFileCodeArea;
23+
import gr.sqlbrowserfx.nodes.codeareas.java.FileJavaCodeArea;
24+
import gr.sqlbrowserfx.nodes.codeareas.sql.CSqlCodeArea;
25+
import gr.sqlbrowserfx.nodes.codeareas.sql.FileSqlCodeArea;
26+
import gr.sqlbrowserfx.nodes.sqlpane.CustomPopOver;
27+
import gr.sqlbrowserfx.nodes.sqlpane.DraggingTabPaneSupport;
28+
import gr.sqlbrowserfx.nodes.sqlpane.SqlTableRowEditBox;
29+
import gr.sqlbrowserfx.nodes.tableviews.SqlTableView;
30+
import gr.sqlbrowserfx.utils.JavaFXUtils;
31+
import javafx.application.Platform;
32+
import javafx.geometry.Orientation;
33+
import javafx.scene.control.Button;
34+
import javafx.scene.control.ContextMenu;
35+
import javafx.scene.control.Label;
36+
import javafx.scene.control.MenuItem;
37+
import javafx.scene.control.ScrollPane;
38+
import javafx.scene.control.SplitPane;
39+
import javafx.scene.control.Tab;
40+
import javafx.scene.control.TabPane;
41+
import javafx.scene.control.TextField;
42+
import javafx.scene.input.KeyCode;
43+
import javafx.scene.input.KeyCombination;
44+
import javafx.scene.input.TransferMode;
45+
import javafx.scene.layout.BorderPane;
46+
47+
public class FilesTabPane extends TabPane {
48+
49+
private CustomPopOver fileSearchPopOver;
50+
private SqlConnector sqlConnector;
51+
52+
public FilesTabPane() {
53+
super();
54+
var draggingSupport = new DraggingTabPaneSupport("/icons/file.png");
55+
draggingSupport.addSupport(this);
56+
57+
this.setOnDragOver(event -> {
58+
if (event.getGestureSource() != this && event.getDragboard().hasFiles()) {
59+
/* allow for both copying and moving, whatever user chooses */
60+
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
61+
}
62+
event.consume();
63+
});
64+
65+
this.setOnDragDropped(event -> {
66+
var db = event.getDragboard();
67+
var success = false;
68+
if (db.hasFiles()) {
69+
var file = db.getFiles().get(0);
70+
this.openNewFileTab(file);
71+
success = true;
72+
}
73+
/*
74+
* let the source know whether the string was successfully transferred and used
75+
*/
76+
event.setDropCompleted(success);
77+
78+
event.consume();
79+
});
80+
81+
Nodes.addInputMap(this, InputMap.consume(EventPattern.keyPressed(KeyCode.O, KeyCombination.CONTROL_DOWN),
82+
action -> this.showFileSearchPopOver()));
83+
}
84+
85+
public void setSqlConnector(SqlConnector sqlConnector) {
86+
this.sqlConnector = sqlConnector;
87+
}
88+
89+
private String fixQuery(String query) {
90+
int spacesNum = 0;
91+
query = query.trim().replaceAll("\t", " ");
92+
for (int i = 0; i < query.length(); i++) {
93+
if (query.charAt(i) == ' ' || query.charAt(i) == '\n') {
94+
spacesNum++;
95+
} else {
96+
break;
97+
}
98+
}
99+
query = query.substring(spacesNum);
100+
//FIXME find right pattern to ignore comments
101+
query = query.replaceAll("--.*\n", "");
102+
return query;
103+
}
104+
105+
public void openQueryTabArea() {
106+
var sqlCodeArea = new CSqlCodeArea();
107+
var tableView = new SqlTableView(this.sqlConnector);
108+
var split = new SplitPane(sqlCodeArea, tableView);
109+
split.setOrientation(Orientation.VERTICAL);
110+
var bPane = new BorderPane(split);
111+
112+
Nodes.addInputMap(tableView, InputMap.consume(EventPattern.keyPressed(KeyCode.C, KeyCombination.CONTROL_DOWN),
113+
action -> {
114+
if (tableView.getSelectionModel().getSelectedCells().isEmpty()) {
115+
return;
116+
}
117+
var selectedItems = tableView.getSelectionModel().getSelectedItems();
118+
if (selectedItems != null) {
119+
var joined = StringUtils.join(selectedItems.stream().map(i -> i.toString()).toList(), "\n");
120+
var stringSelection = new StringSelection(joined);
121+
var clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
122+
clipboard.setContents(stringSelection, null);
123+
}
124+
}));
125+
126+
var popOver =new CustomPopOver();
127+
tableView.setOnMouseClicked(event -> {
128+
if (event.getClickCount() < 2) {
129+
return;
130+
}
131+
132+
var sqlTableRow = tableView.getSelectionModel().getSelectedItem();
133+
if (sqlTableRow == null)
134+
return;
135+
136+
var editBox = new SqlTableRowEditBox(tableView, sqlTableRow, false);
137+
var sp = new ScrollPane(editBox);
138+
sp.setMaxHeight(800);
139+
sp.setFitToWidth(true);
140+
141+
popOver.setContentNode(sp);
142+
popOver.show(tableView, event.getScreenX(), event.getScreenY());
143+
});
144+
145+
var stopBtn = new Button("", JavaFXUtils.createIcon("/icons/stop.png"));
146+
147+
var sqlQueryRunning = new AtomicBoolean(false);
148+
sqlCodeArea.setRunAction(() -> {
149+
final var fixedQuery = this.fixQuery(sqlCodeArea.getSelectedText().isBlank() ? sqlCodeArea.getText() : sqlCodeArea.getSelectedText());
150+
sqlCodeArea.setDisable(true);
151+
tableView.setDisable(true);
152+
153+
bPane.setBottom(new CustomHBox(new Label("Running query. Please wait..."), stopBtn));
154+
var queryDuration = new AtomicLong(System.currentTimeMillis());
155+
if (sqlConnector != null && (fixedQuery.toLowerCase().startsWith("select") || fixedQuery.toLowerCase().startsWith("show"))) {
156+
sqlConnector.executeAsync(() -> {
157+
sqlQueryRunning.set(true);
158+
try {
159+
sqlConnector.executeCancelableQuery(fixedQuery, rset -> {
160+
queryDuration.set(System.currentTimeMillis() - queryDuration.get());
161+
LoggerFactory.getLogger(LoggerConf.LOGGER_NAME).debug("\n" + fixedQuery + "\n execution took " + queryDuration.get() + "ms");
162+
DialogFactory.createNotification("Query executed", "Query execution took " + queryDuration.get() + "ms", 1);
163+
tableView.setItemsLater(rset);
164+
}, stmt -> {
165+
stopBtn.setOnAction(action -> {
166+
try {
167+
stmt.cancel();
168+
} catch (SQLException e) {
169+
DialogFactory.createErrorDialog(e);
170+
}
171+
});
172+
});
173+
174+
} catch (SQLException e) {
175+
DialogFactory.createErrorDialog(e);
176+
} finally {
177+
sqlQueryRunning.set(false);
178+
Platform.runLater(() -> {
179+
sqlCodeArea.setDisable(false);
180+
tableView.setDisable(false);
181+
bPane.setBottom(new Label(tableView.getSqlTableRows().size() + " rows"));
182+
});
183+
}
184+
});
185+
}
186+
else if (!fixedQuery.isEmpty()) {
187+
sqlConnector.executeAsync(() -> {
188+
sqlQueryRunning.set(true);
189+
try {
190+
int rowsAffected = sqlConnector.executeUpdate(fixedQuery);
191+
queryDuration.set(System.currentTimeMillis() - queryDuration.get());
192+
LoggerFactory.getLogger(LoggerConf.LOGGER_NAME).debug("\n" + fixedQuery + "\n execution took " + queryDuration.get() + "ms");
193+
DialogFactory.createNotification("Query executed", "Query execution took " + queryDuration.get() + "ms\n(" + rowsAffected + ") rows affected" , 3);
194+
195+
} catch (SQLException e) {
196+
DialogFactory.createErrorDialog(e);
197+
} finally {
198+
Platform.runLater(() -> sqlCodeArea.setDisable(false));
199+
sqlQueryRunning.set(false);
200+
}
201+
});
202+
}
203+
});
204+
205+
206+
var tab = new Tab("Sql Query", bPane);
207+
tab.setGraphic(JavaFXUtils.createIcon("/icons/thunder.png"));
208+
addTabContextMenu(tab);
209+
this.getTabs().add(tab);
210+
this.getSelectionModel().select(tab);
211+
}
212+
213+
private void addTabContextMenu(Tab tab) {
214+
var closeTabItem = new MenuItem("Close Tab", JavaFXUtils.createIcon("/icons/minus.png"));
215+
closeTabItem.setOnAction(event -> tab.getTabPane().getTabs().remove(tab));
216+
217+
var renameTabItem = new MenuItem("Rename Tab", JavaFXUtils.createIcon("/icons/edit.png"));
218+
renameTabItem.setOnAction(event -> {
219+
var tabGraphic = tab.getGraphic();
220+
var textField = new TextField();
221+
textField.setPromptText("Enter new name");
222+
textField.setOnKeyPressed(keyEvent -> {
223+
if (keyEvent.getCode() == KeyCode.ENTER) {
224+
// graphic is label because we are using DragTabPaneSupport util
225+
var label = (Label) tabGraphic;
226+
label.setText(textField.getText());
227+
tab.setGraphic(tabGraphic);
228+
}
229+
if (keyEvent.getCode() == KeyCode.ESCAPE) {
230+
tab.setGraphic(tabGraphic);
231+
}
232+
233+
keyEvent.consume();
234+
});
235+
tab.setGraphic(textField);
236+
textField.requestFocus();
237+
});
238+
239+
tab.setContextMenu(new ContextMenu(closeTabItem, renameTabItem));
240+
}
241+
242+
public void openNewFileTab(File selectedFile) {
243+
var tab = new Tab(selectedFile.getName());
244+
245+
AutoCompleteCodeArea<?> codeArea;
246+
if (selectedFile.getName().endsWith(".java")) {
247+
codeArea = new FileJavaCodeArea(selectedFile);
248+
} else if (selectedFile.getName().endsWith(".sql")) {
249+
codeArea = new FileSqlCodeArea(selectedFile);;
250+
} else {
251+
codeArea = new SimpleFileCodeArea(selectedFile);
252+
}
253+
254+
255+
var vsp = new VirtualizedScrollPane<>(codeArea);
256+
tab.setContent(vsp);
257+
var fileCodeArea = (FileCodeArea) codeArea;
258+
259+
tab.setOnCloseRequest((event) -> {
260+
if (fileCodeArea.isTextDirty()) {
261+
event.consume();
262+
263+
if (DialogFactory.createConfirmationDialog(
264+
"Unsaved work",
265+
"Do you want to discard changes ?")
266+
) {
267+
this.getTabs().remove(tab);
268+
}
269+
}
270+
});
271+
var closeTabItem = new MenuItem("Close Tab", JavaFXUtils.createIcon("/icons/minus.png"));
272+
closeTabItem.setOnAction(event -> {
273+
if (fileCodeArea.isTextDirty()) {
274+
event.consume();
275+
276+
if (DialogFactory.createConfirmationDialog(
277+
"Unsaved work",
278+
"Do you want to discard changes ?")
279+
) {
280+
this.getTabs().remove(tab);
281+
}
282+
}
283+
});
284+
tab.setContextMenu(new ContextMenu(closeTabItem));
285+
286+
tab.setGraphic(JavaFXUtils.createIcon("/icons/code-file.png"));
287+
this.getTabs().add(tab);
288+
this.getSelectionModel().select(tab);
289+
290+
codeArea.requestFocus();
291+
}
292+
293+
private void showFileSearchPopOver() {
294+
if (this.fileSearchPopOver == null) {
295+
this.fileSearchPopOver = new FileSearchPopOver(this::openNewFileTab);
296+
}
297+
298+
if (this.fileSearchPopOver.isShowing()) {
299+
return;
300+
}
301+
302+
var boundsInScene = this.localToScreen(this.getBoundsInLocal());
303+
fileSearchPopOver.show(this, boundsInScene.getMaxX() - 620,
304+
boundsInScene.getMinY());
305+
}
306+
}

0 commit comments

Comments
 (0)