-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Implement task progress indicator (and dialog) in the toolbar #6443
Conversation
Implemented a task progress dialog using a TaskProgressView. Tasks show up, but without info. Neither the progress nor title and message are shown.
There now i a progress indicator at the rightmost postition of JabRefs toolbar. It shows the average progress of all running background tasks. Clicking it will show a TaskProgressDialog. still looks ugly and the binding to the average progress does not seem to be working.
this.backgroundTasks.add(backgroundTask); | ||
} | ||
|
||
public BooleanBinding anyTaskRunningBinding = Bindings.createBooleanBinding( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the bindings to update, you need to add the list as a dependency (second argument of the createXBinding
method). In the case of lists, however, its easier to use EasyBind.combine
: https://github.com/TomasMikula/EasyBind#combine-list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, that looks useful.
However, I think I am running into some typing issues which I am struggling to resolve.
With the library you pointed me to, I wound up with the following:
public Binding<Boolean> anyTaskRunningBinding = EasyBind.combine( backgroundTasks, stream -> stream.anyMatch(Task::getProgress) );
This gives me an error that it expects a Binding, but gets a MonadicBinding.
If I just cast it, I get the following:
no instance(s) of type variable(s) T exist so that Task conforms to ObservableValue<? extends T>
I think this is the issue I need to resolve first. As the example in the library works fine, I guess the conversion from MonadicBinding to Binding is then done implicitly, is that correct?
I struggle solving this because I dont know the type parameter of the tasks I am storing.
When storing, the Task has type V:
But in the list I just use Task:
How do I need to change the list in order for it to work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the generics to the Task as welll:
private final UiThreadObservableList<Task<V>> backgroundTasks = new UiThreadObservableList(FXCollections.observableArrayList());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But where would I get the V from in this case?
Cannot resolve symbol 'V'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to Task<?> now and now the progress, title and message properties make their way through to the dialogue.
I still cannot create the bindings for the progress indicator though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That change also allows me to use EasyBind's listBind to bind the task list in the view to the task list in StateManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good already!
There are a lot of tasks which don't concern file downloads and just perform some operations in the background. If you search for all references from backgrond task you probably have to adjust each one to add a meaningful description
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that should be doable. But before I do that: is it a good idea to have all background tasks listed? Or should we just show downloads? If we go with the first option, I suggest turning off the retain-tasks feature of the task view. Then, all completed tasks will automatically disappear. This way we have less tasks that only ran for a very little time filling up the view.
I got the bindings to work by storing a list of Property<Task> Instead of Task. That does the trick for the progress indicator. It now is indeterminate when one of the tasks has an indeterminate progress and shows the average progress otherwise (100% if no tasks are running).
For some reason, this breaks the task view. Since I now store a list of properties, and not a list of tasks, I cannot bind them directly to the view, so I went back to doing it manually, but that does not seem to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I just converted the list of properties back into a list of tasks using EasyBind and the bind that list to the list of tasks in the view (again using EasyBind). Now both the indicator and the dialogue work fine and are updated with the running tasks!
indicator.setOnMouseClicked(new EventHandler<MouseEvent>() { | ||
@Override | ||
public void handle(MouseEvent event) { | ||
TaskProgressDialog taskProgressDialog = new TaskProgressDialog(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using a real dialog, what about using a collapse overlay similar to how it's done in firefox?
https://github.com/controlsfx/controlsfx/wiki/ControlsFX-Features#popover
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh fancy! I'll look into it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes the view work with download tasks for example. Most other tasks are still shown without title, message (because none are set) and progress. Also, there are a lot of tasks somehow. The progress indicator in the main view still does not work as I can't get the bindings to work.
The progress indicator is now successfully bound to the list of tasks. However, tasks do not show up in the dialogue any more.
When using ObjectProperties in the list of tasks, we can use EasyBind to convert the list into a list of tasks which in turn can be bound to the list of tasks in the view. With this, the basic functionality works. There is a progress indicator in the toolbar that shows the average progress of all running background tasks. It is indeterminate if any task has indeterminate progress and shows 100% if no tasks are running. Clicking it opens an overview of all running tasks and their progress. Currently, there are many tasks running all the time. The only tasks that were adapted for this to be pretty are the download tasks, and they are also still missing an icon.
I would only add some tasks. Most tasks which are run in the background are irrelevant to the user |
These are shorter and therefore the task progress view does not need a horizontal scroll bar.
I would say: display all tasks that have a title (or put a property in the task: visible). So in theory, you could also add another button "show all" (or something similar). |
Btw. I think this is a huge improvement to JabRef. Thank you for your effort. |
I went with a property showToUser, a button to show all tasks does sound interesting, but I think that one only makes sense if all tasks set the message and title properties. When looking at the task-list, I guess that might be many pieces of code to adapt. Maybe it is better not to do that at first and maybe add that functionality later. |
Glad to be of help. It's a welcome distraction from my master's thesis :) |
Yeah. This is exactly how I came (back) to JabRef and JabRef programming. 😆 |
@@ -41,6 +48,7 @@ | |||
private final OptionalObjectProperty<SearchQuery> activeSearchQuery = OptionalObjectProperty.empty(); | |||
private final ObservableMap<BibDatabaseContext, IntegerProperty> searchResultMap = FXCollections.observableHashMap(); | |||
private final OptionalObjectProperty<Node> focusOwner = OptionalObjectProperty.empty(); | |||
private final UiThreadObservableList<ObjectProperty<Task<?>>> backgroundTasks = new UiThreadObservableList(FXCollections.observableArrayList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason to wrap the tasks around in a ObjectProperty
? It should also work without this wrapper. If there were problems with the updates, you might need to add an "extractor" to the observableArrayList
which specifies that the list should update if the underlying data changes (in this case probably the progress of the task).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. I though EasyBind's combineList takes care of updating the observable when the progress updates? From the doc:
Turns an observable list of observable values into a single observable value. The resulting observable value is updated when elements are added or removed to or from the list, as well as when element values change.
I had to turn it into a list of ObjectProperty because that's what EasyBind's combineList expects, but I guess it then only registers changes of the ObjectProperty, not the task.
So to update upon progress changes, I need to tell the list that I am interested in the progress by defining an extractor. I found some code online working with extractors, so I think I should be able to implement that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, combineList takes updates into account but for this the elements have to observable themselves. For example, if ObservableList<ObservableDoubleValue>
is a list containing the progress
property of each task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About the ObjectProperty wrapping issue. I tried changing it back just to see what happens, and I still get the same error for the bindings:
no instance(s) of type variable(s) T exist so that Task<?> conforms to ObservableValue<? extends T>
I think that the combine method only works on lists of observables.
If I create the binding with Bindings.createDoubleBinding, it does not update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So
public Binding<Double> tasksProgressBinding = Bindings.createDoubleBinding(
backgroundTasks.filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1), backgroundTasks);`
doesn't work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it does (except for some minor mistakes in your code).
I did not put the list name (backgroundTasks) as a second argument. Does that mean that the binding would have worked ONLY on the extractor? So adding and removing does not update the value, but a change in the progress would have? Or why else do we need to pass the list as a second argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second argument specifies the observables that the function (the first argument) depends on. That is, every time these observables change, the function is called and the binding is updated with the new value. If you don't specify any observables in the second argument, then the binding is never updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that makes sense! Thanks for explaining!
For consistency with other variables
Make showProgressDialogAndWait actually not only show but also wait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again! Looks really good to me. Your solution for the width problem is also fine for me.
I have a few remarks, but nothing big.
DialogPane contentPane = new DialogPane(); | ||
contentPane.setContent(box); | ||
|
||
FXDialog alert = new FXDialog(Alert.AlertType.NONE, Localization.lang("Please wait...")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/gui/DialogService.java#L179 (or the other overload) cannot be used here? I think it would be slightly cleaner to make WaitForBackgroundtasksFinishedDialog
a "real" dialog (i.e. inherit from Dialog
) or even convert it to a proper fxml-based dialog similar to most dialogs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did use the custom dialogue at the beginning, but it gives no access to the actual javafx dialogue, meaning I cannot hide it on an event. The thing with the JabRef dialogues created by the show*AndWait is that they create the actual javafx dialogue in the method, so they can't be accessed later. I just found the createDialog method which would probably be the best to use here. It creates the dialog and takes care of some styling and such, and then returns the actual dialog which can then be shown and waited for. I'll do that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this ok? I tried to follow how showProgressDialogAndWait is implemented.
ff9ce00
@@ -27,14 +33,29 @@ | |||
* @param <V> type of the return value of the task | |||
*/ | |||
public abstract class BackgroundTask<V> { | |||
|
|||
public static ImmutableMap<String, Node> iconMap = ImmutableMap.of( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you prefer a map here instead of adding a new property icon
to the BackgroundTask class (so that downloadTask.setIcon(IconTheme.JabRefIcons.DOWNLOAD)
works)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's because the TaskProgressView works on a list of tasks. Tasks have no icon property, therefore one must provide a callback that gets an icon from a task.
If BackgroundTask were derived from Task, we could add a property to BackgroundTask. The callback could then check if we have a BackgroundTask at hand and if so, provide the icon stored in a property in BackgroundTask. But BackgroundTask is not derived from Task so this is not possible.
We need a key to map from a Task to an Icon.
I used the title of the task as a key property to map to the icon, so an immutable map seemed like the best way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I see. It's a bit unfortunate but your solution makes sense. I would propose to change the Callback
below to a normal method (public static Node getIcon(Task<?> task)
which you then use as a callback using BackgroundTask::getIcon
) and replace the iconMap
by a normal switch statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to method is easy, but I cannot use a switch, because I would need a constant expression. As I use the title of the task, and there is localization on that, it is not constant.
396411a
More in line with the other JabRef dialogs Addresses JabRef#6443 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the quick follow-up! Looks very good to me new.
(I've slightly changed the getIcon
method - hopefully without destroying anything).
Great! Thanks for the quick review! There is a reason though I passed Objects to getIcon, that's the signature needed for setGraphicFactory in TaskProgressView. |
Ok I see, can you then please revert my two last commits (stupid me...sorry actually wanted to help :() |
Happens to the best :) I have a couple of questions that are kind of off-topic here, I hope you don't mind:
Completely unrelated:
|
I am already done and just create a PR. I hope that is fine with you. |
It's a problem with our build server (no space left). You can safely ignore it.
The easiest way is to manually download the deb file or use snap.
We had problems with the official ubuntu repo. This is why the version there is pretty old (3.8 if I remember correctly). If you are interested, feel free to revisit the problem - maybe now that we provide deb packages this might be easier. @koppor may tell you more about the issues. Refs #1371
You don't need to create a issue before submitting a PR. |
Localization.lang("Please wait..."), | ||
Localization.lang("Waiting for background tasks to finish. Quit anyway?"), | ||
stateManager | ||
).orElse(ButtonType.CANCEL) == ButtonType.YES)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks odd to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate?
If you mean the condition at the end, I just want to make sure that JabRef only exists if the user pressed yes. So if the optional is empty I just put ButtonType.CANCEL so the comparison to YES returns false.
Do you want me to change this or add a comment? Maybe it would be clearer to store the result in a variable and check for (result.isPresent() && result.get() == ButtonType.YES).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this is more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 3db3997
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general looks good to me, there is only that one piece of code which looks wrong or odd to me
The one I was using on Ubuntu 20.04 is 5.0.50001-1. Thats what you get from the official repos. |
Thanks again! |
Thanks for all your help along the way and for JabRef! |
* upstream/master: (50 commits) Keep group pane size when resizing window (#6180) (#6423) Changelog: Fix missing citation for biblatex-mla Update AUTHORS Check duplicate DOI (#6333) Fix missing citation for biblatex-mla Change EasyBind dependency (#6480) Add testing of latest dev version as mandatory Squashed 'src/main/resources/csl-styles/' changes from 5dad23d..586e0b8 Fix libre office connection and other progress dialogs (#6478) Fix clear year and month field when converting to biblatex (#6434) Add truncate as a BibTex key modifier (#6427) Add new authors (not all - they need more work) Remove empty line Add simple Unit Tests for #6207 (#6240) Enforce LeftCurly rule (#6452) Implement task progress indicator (and dialog) in the toolbar (#6443) Consider empty brackets Changelog update Added a test Fixed brackets in regular expressions ...
Implements a background-task progress indicator in JabRefs toolbar as first discussed in #6381 (comment).
The indicator is located at the right-most position in the toolbar. Clicking it opens a pop-over that lists the background tasks with an icon, title, message and progress.
In order not to overwhelm the user with background tasks, only interesting ones are shown. To make a background task show, it needs to have the showToUser property set. For it to have a meaningful entry, one should set the title and message to appropriate values. If progress can be tracked, that should be done as well. If not, an indeterminate indicator / progress bar is shown.
To set the icon of the task, add a mapping from the task-title to an icon to the iconMap in BackgroundTask.java