Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue: right click menu 6601 #9271

Merged
merged 10 commits into from
Dec 5, 2022
10 changes: 10 additions & 0 deletions src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ public MainTable(MainTableDataModel model,
});

database.getDatabase().registerListener(this);

MainTableColumnFactory rightClickMenuFactory = new MainTableColumnFactory(
database,
preferencesService,
preferencesService.getColumnPreferences(),
libraryTab.getUndoManager(),
dialogService,
stateManager);
// Enable the header right-click menu.
new MainTableHeaderContextMenu(this, rightClickMenuFactory).show(true);
}

/**
Expand Down
85 changes: 45 additions & 40 deletions src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,50 +74,55 @@ public MainTableColumnFactory(BibDatabaseContext database,
this.stateManager = stateManager;
}

public TableColumn<BibEntryTableViewModel, ?> createColumn(MainTableColumnModel column) {
TableColumn<BibEntryTableViewModel, ?> returnColumn = null;
switch (column.getType()) {
case INDEX:
returnColumn = createIndexColumn(column);
break;
case GROUPS:
returnColumn = createGroupColumn(column);
break;
case FILES:
returnColumn = createFilesColumn(column);
break;
case LINKED_IDENTIFIER:
returnColumn = createIdentifierColumn(column);
break;
case LIBRARY_NAME:
returnColumn = createLibraryColumn(column);
break;
case EXTRAFILE:
if (!column.getQualifier().isBlank()) {
returnColumn = createExtraFileColumn(column);
}
break;
case SPECIALFIELD:
if (!column.getQualifier().isBlank()) {
Field field = FieldFactory.parseField(column.getQualifier());
if (field instanceof SpecialField) {
returnColumn = createSpecialFieldColumn(column);
} else {
LOGGER.warn("Special field type '{}' is unknown. Using normal column type.", column.getQualifier());
returnColumn = createFieldColumn(column);
}
}
break;
default:
case NORMALFIELD:
if (!column.getQualifier().isBlank()) {
returnColumn = createFieldColumn(column);
}
break;
}
return returnColumn;
}

public List<TableColumn<BibEntryTableViewModel, ?>> createColumns() {
List<TableColumn<BibEntryTableViewModel, ?>> columns = new ArrayList<>();

columnPreferences.getColumns().forEach(column -> {

switch (column.getType()) {
case INDEX:
columns.add(createIndexColumn(column));
break;
case GROUPS:
columns.add(createGroupColumn(column));
break;
case FILES:
columns.add(createFilesColumn(column));
break;
case LINKED_IDENTIFIER:
columns.add(createIdentifierColumn(column));
break;
case LIBRARY_NAME:
columns.add(createLibraryColumn(column));
break;
case EXTRAFILE:
if (!column.getQualifier().isBlank()) {
columns.add(createExtraFileColumn(column));
}
break;
case SPECIALFIELD:
if (!column.getQualifier().isBlank()) {
Field field = FieldFactory.parseField(column.getQualifier());
if (field instanceof SpecialField) {
columns.add(createSpecialFieldColumn(column));
} else {
LOGGER.warn("Special field type '{}' is unknown. Using normal column type.", column.getQualifier());
columns.add(createFieldColumn(column));
}
}
break;
default:
case NORMALFIELD:
if (!column.getQualifier().isBlank()) {
columns.add(createFieldColumn(column));
}
break;
}
columns.add(createColumn(column));
});

return columns;
Expand Down
184 changes: 184 additions & 0 deletions src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package org.jabref.gui.maintable;

import java.util.ArrayList;
import java.util.List;

import javafx.scene.control.ContextMenu;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.layout.StackPane;

import org.jabref.gui.maintable.columns.MainTableColumn;

public class MainTableHeaderContextMenu extends ContextMenu {

MainTable mainTable;
MainTableColumnFactory factory;

/**
* Constructor for the right click menu
*
*/
public MainTableHeaderContextMenu(MainTable mainTable, MainTableColumnFactory factory) {
super();
this.mainTable = mainTable;
this.factory = factory;
constructItems(mainTable);
}

/**
* Handles showing the menu in the cursors position when right-clicked.
*/
public void show(boolean show) {
// TODO: 20/10/2022 unknown bug where issue does not show unless parameter is passed through this method.
mainTable.setOnContextMenuRequested(event -> {
// Display the menu if header is clicked, otherwise, remove from display.
if (!(event.getTarget() instanceof StackPane) && show) {
this.show(mainTable, event.getScreenX(), event.getScreenY());
} else if (this.isShowing()) {
this.hide();
}
event.consume();
});
}

/**
* Constructs the items for the list and places them in the menu.
*/
private void constructItems(MainTable mainTable) {
// Reset the right-click menu
this.getItems().clear();

// Populate the menu with the commonly used fields
for (TableColumn<BibEntryTableViewModel, ?> tableColumn:commonColumns()) {
koppor marked this conversation as resolved.
Show resolved Hide resolved
RadioMenuItem itemToAdd = createMenuItem(tableColumn);
this.getItems().add(itemToAdd);
}

SeparatorMenuItem separator = new SeparatorMenuItem();
this.getItems().add(separator);

// Append to the menu the current remaining columns in the table.
for (TableColumn<BibEntryTableViewModel, ?> column :mainTable.getColumns()
) {
// Append only if the column has not already been added (a common column)
if (!isACommonColumn((MainTableColumn) column)) {
RadioMenuItem itemToAdd = createMenuItem(column);
this.getItems().add(itemToAdd);
}
}
}

/**
* Creates an item for the menu constructed with the name/visibility of the table column.
*
*/
@SuppressWarnings("rawtypes")
private RadioMenuItem createMenuItem(TableColumn<BibEntryTableViewModel, ?> column) {
// Construct initial menuItem
MainTableColumn tableColumn = (MainTableColumn) column;
String itemName = tableColumn.getDisplayName();

RadioMenuItem returnItem = new RadioMenuItem(itemName);

// Flag item as selected if the item is already in the main table.
returnItem.setSelected(isInMainTable(tableColumn));

// Set action to toggle visibility from main table when item is clicked
returnItem.setOnAction(event -> {
if (isInMainTable(tableColumn)) {
removeColumn(tableColumn);
} else {
addColumn(tableColumn);
}
});

return returnItem;
}

/**
* Adds the column into the MainTable for display.
*/
@SuppressWarnings("rawtypes")
private void addColumn(MainTableColumn tableColumn) {
// Do not add duplicate if table column is already within the table.
if (!isInMainTable(tableColumn)) {
mainTable.getColumns().add(tableColumn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the user first removes a column, and then immediately readds it (e.g. he realizes removing it was a mistake). In this case, the column order should be the same as it was before. If I read the code correctly, then currently the column would be appended at the end instead of at its original position.

Copy link
Contributor Author

@ethantr ethantr Nov 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made some more changes here with my current commit. When the user goes to click to remove the column, it obtains the current index in the MainTable and stores that number inside the Menu Item. If he wants to immediately place it back, it will be placed back in that exact position using that index.
If the items addjacent to it are removed, it will still be placed in the position of the given index - not sure if that will be confusing to the end user or not, whether it should be done in this way, or another. happy to hear your thoughts.

}
}

/**
* Removes the column from the MainTable to remove visibility.
*/
@SuppressWarnings("rawtypes")
private void removeColumn(MainTableColumn tableColumn) {
mainTable.getColumns().removeIf(tableCol -> ((MainTableColumn) tableCol).getModel().equals(tableColumn.getModel()));
}

/**
* Determines if column already exists in the MainTable.
*/
private boolean isInMainTable(MainTableColumn tableColumn) {
return isColumnInList(tableColumn, mainTable.getColumns());
}

/**
* Checks if a column is one of the commonly used columns.
*/
private boolean isACommonColumn(MainTableColumn tableColumn) {
return isColumnInList(tableColumn, commonColumns());
}

/**
* Determines if a list of TableColumns contains the searched column.
*/
private boolean isColumnInList(MainTableColumn searchColumn, List<TableColumn<BibEntryTableViewModel, ?>> tableColumns) {
for (TableColumn<BibEntryTableViewModel, ?> column:
tableColumns) {
MainTableColumnModel model = ((MainTableColumn) column).getModel();
if (model.equals(searchColumn.getModel())) {
return true;
}
}
return false;
}

/**
* Creates the list of the "commonly used" columns (currently based on the default preferences).
*/
private List<TableColumn<BibEntryTableViewModel, ?>> commonColumns() {
// Qualifier strings
String entryTypeQualifier = "entrytype";
String authorEditQualifier = "author/editor";
String titleQualifier = "title";
String yearQualifier = "year";
String journalBookQualifier = "journal/booktitle";
String rankQualifier = "ranking";
String readStatusQualifier = "readstatus";
String priorityQualifier = "priority";

// Create the MainTableColumn Models from qualifiers + types.
List<MainTableColumnModel> commonColumns = new ArrayList<>();
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.GROUPS));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.FILES));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.LINKED_IDENTIFIER));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, entryTypeQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, authorEditQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, titleQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, yearQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, journalBookQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, rankQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, readStatusQualifier));
commonColumns.add(new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, priorityQualifier));

// Create the Table Columns from the models using factory methods.
List<TableColumn<BibEntryTableViewModel, ?>> commonTableColumns = new ArrayList<>();
for (MainTableColumnModel columnModel: commonColumns
) {
calixtus marked this conversation as resolved.
Show resolved Hide resolved
TableColumn<BibEntryTableViewModel, ?> tableColumn = factory.createColumn(columnModel);
commonTableColumns.add(tableColumn);
}
return commonTableColumns;
}
}