Merge PDFPC features (#878)

* added commandline support to open documents (#802)

* added commandline support to open documents

* fixes to show Error PopUp

* Feature/overlay (#803)

* overlay prototype

* changed color
removed sysout

* removed popup

* Bugfix for single line slides
readability improved

* Feature/notes (#843)

* added split for right notes

* set default slide position to UNKNOWN

* added menu to switch modes

* added left notes

* added new Tab and external window for note slide

* minor refactoring
correct menu selection

* center slideNotesView

* render left side correct

* removed workaround and output

* fix merge imports

---------

Co-authored-by: Alex Andres <58339654+devopvoid@users.noreply.github.com>

* Fixed merge conflict with slide notes feature

* Feature/bookmarks (#842)

* added toolbar actions for bookmark navigation

* improved toolbar actions for bookmark navigation

* typo fix

* added icons and remove bookmark toolbar button

* handle delete for not bookmarked pages

* reverted buttons in preview
minor fixes

* added bookmark removed info to menu

* fix menuitem for nextBookmark

* fixed errorHandling and added unittests

---------

Co-authored-by: Dustin Ringel <Dustin.Ringel@student.hs-rm.de>
Co-authored-by: Alex Andres <58339654+devopvoid@users.noreply.github.com>

* Fixed merge conflict with the bookmarks feature

* Fixed action GC in ExternalFrame #777

* Fixed 'view' menu translations and item order #778

* Fixed goto bookmark

* Show notes slide above the thumbnails

* Fixed note slide positioning

* Fixed updating canvas bounds for the StylusListener

* Refactored view position handling

* Move note slide with the slide preview

* Move peer view with the slide preview

* Refactored split notes handling

* Improved determining the note slide

* Document: do not set note slide position if there are no note slides

* Do not show note slides when the document has none

* Merged key bindings

* Merged goto slide number

* Fixed NotificationPopup size and position

* Minor split-view fix in the slide-view

* Fixed view component movement in SwingSlidesView

* Fixed MainPresenterTest

* Minor translation fixes

* Fixed slide preview docking

* Fixed external frame switching

* Fixed external frame switching for note slide and slide preview

* Fixed external note slide frame closing

---------

Co-authored-by: krueml0815 <66517311+krueml0815@users.noreply.github.com>
Co-authored-by: Dustin Ringel <Dustin.Ringel@student.hs-rm.de>
This commit is contained in:
Alex Andres 2024-02-08 19:33:36 +01:00 committed by GitHub
parent d7059fbfe9
commit fea24d42ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
95 changed files with 3092 additions and 919 deletions

View file

@ -108,9 +108,7 @@ public abstract class ApplicationBase implements Application {
File file = new File(utf8Path);
if (file.exists()) {
OPEN_FILES.add(file);
}
OPEN_FILES.add(file);
}
try {

View file

@ -107,6 +107,7 @@ public abstract class ApplicationContext {
ppProvider.put(ViewType.User, new PresentationParameterProvider(config));
ppProvider.put(ViewType.Preview, new PresentationParameterProvider(config));
ppProvider.put(ViewType.Presentation, new PresentationParameterProvider(config));
ppProvider.put(ViewType.Slide_Notes, new PresentationParameterProvider(config));
}
/**

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.core.bus.event;
import org.lecturestudio.core.model.Page;
public class BookmarkEvent extends BusEvent {
/** Enum with the {@link BookmarkEvent} types. */
public enum Type { CREATED, REMOVED};
/** the {@link Type} of the {@link BookmarkEvent}. */
private final Type type;
private final Page page;
/**
* Create the {@link BookmarkEvent} with specified page and type.
*
* @param page The page.
* @param type The type of the {@link BookmarkEvent}.
*/
public BookmarkEvent(Page page, BookmarkEvent.Type type) {
this.page = page;
this.type = type;
}
/**
* Get the type of the {@link BookmarkEvent}.
*
* @return The type of the {@link BookmarkEvent}.
*/
public Type getType() {
return type;
}
/**
* Get the page.
*
* @return The page.
*/
public Page getPage() {
return page;
}
/**
* Indicates whether the {@link BookmarkEvent} is created.
*
* @return {@code true} if the {@link #type} equals {@code Type.CREATED}, otherwise {@code false}.
*/
public boolean isCreated() {
return type == Type.CREATED;
}
/**
* Indicates whether the {@link BookmarkEvent} is removed.
*
* @return {@code true} if the {@link #type} equals {@code Type.REMOVED}, otherwise {@code false}.
*/
public boolean isRemoved() {
return type == Type.REMOVED;
}
}

View file

@ -109,7 +109,12 @@ public class RenderController extends Controller {
final PresentationParameterProvider ppProvider = getContext().getPagePropertyProvider(viewType);
final PresentationParameter parameter = ppProvider.getParameter(page);
page.getDocument().getDocumentRenderer().render(page, parameter, image);
if (viewType == ViewType.Slide_Notes){
page.getDocument().getDocumentRenderer().renderNotes(page, parameter, image);
}
else {
page.getDocument().getDocumentRenderer().render(page, parameter, image);
}
if (page.getDocument().isWhiteboard()) {
renderGrid(image, parameter, viewType);

View file

@ -18,6 +18,7 @@
package org.lecturestudio.core.model;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.io.File;
@ -27,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;
import org.lecturestudio.core.geometry.Dimension2D;
import org.lecturestudio.core.geometry.Rectangle2D;
@ -92,6 +94,11 @@ public class Document {
/** The unique ID of this document. */
private UUID uid;
/** Position of notes */
private NotesPosition splitSlideNotesPosition = NotesPosition.UNKNOWN;
/** Position of Notes in new Documents for export. Setting the splitSlideNotesPosition variable ends in broken PDF. */
private NotesPosition actualSplitSlideNotesPosition = NotesPosition.UNKNOWN;
/**
* Create a new {@link Document}.
@ -255,7 +262,7 @@ public class Document {
* @return The media box of the specified page.
*/
public Rectangle2D getPageRect(int pageIndex) {
return pdfDocument.getPageMediaBox(pageIndex);
return pdfDocument.getPageMediaBox(pageIndex, splitSlideNotesPosition);
}
/**
@ -277,7 +284,7 @@ public class Document {
* @return A list of the text positions.
*/
public List<Rectangle2D> getTextPositions(int pageIndex) {
return pdfDocument.getNormalizedWordPositions(pageIndex);
return pdfDocument.getNormalizedWordPositions(pageIndex, splitSlideNotesPosition);
}
/**
@ -493,6 +500,17 @@ public class Document {
return importPage(page, pageRect);
}
public boolean hasNoteSlide() {
Page p = getPage(0);
if (isNull(p)) {
return false;
}
Rectangle2D bounds = pdfDocument.getPageBounds(0);
return bounds.getWidth() / bounds.getHeight() >= 2;
}
/**
* Set the type of the document.
*
@ -719,12 +737,28 @@ public class Document {
setDocumentType(DocumentType.PDF);
loadPages();
loadNoteSlidePosition();
}
private void loadNoteSlidePosition() {
if (getSplitSlideNotesPosition() == NotesPosition.UNKNOWN) {
if (hasNoteSlide()) {
setSplitSlideNotesPosition(NotesPosition.RIGHT);
}
else {
setSplitSlideNotesPosition(NotesPosition.NONE);
}
calculateCropBox();
}
}
private void loadPages() {
pages.clear();
int pageCount = pdfDocument.getPageCount();
String[] prevSplitPageText = new String[2];
String[] splitPageText;
for (int number = 0; number < pageCount; number++) {
Page page = new Page(this, number);
@ -737,6 +771,26 @@ public class Document {
}
}
splitPageText = page.getPageText().split("\n");
if (splitPageText.length >= 2 && prevSplitPageText.length >= 2 &&
Stream.of(prevSplitPageText[0], prevSplitPageText[1], splitPageText[0], splitPageText[1])
.allMatch(Objects::nonNull)) {
if (prevSplitPageText[0].equals(splitPageText[0]) && prevSplitPageText[1].equals(splitPageText[1])) {
page.setOverlay(true);
if (number > 0) {
Page prevPage = pages.get(number - 1);
prevPage.setOverlay(true);
}
}
else {
prevSplitPageText = splitPageText;
}
}
else {
prevSplitPageText = splitPageText;
}
pages.add(page);
}
@ -745,4 +799,62 @@ public class Document {
}
}
/**
* Calculates the crop-box for all pages, depending on splitSlideNotesPosition variable.
*/
public void calculateCropBox() {
int width;
int height;
if (splitSlideNotesPosition == NotesPosition.LEFT) {
for (int i = 0; i < getPageCount(); i++) {
width = (int) getPageRect(i).getWidth();
height = (int) getPageRect(i).getHeight();
pdfDocument.setCropbox(i, width, 0, width, height);
}
}
else if (splitSlideNotesPosition == NotesPosition.RIGHT || splitSlideNotesPosition == NotesPosition.NONE) {
for (int i = 0; i < getPageCount(); i++) {
width = (int) getPageRect(i).getWidth();
height = (int) getPageRect(i).getHeight();
pdfDocument.setCropbox(i, 0, 0, width, height);
}
}
}
/**
* Get the position of the notes when the slide needs to be split
*
* @return position of the notes on the slide
*/
public NotesPosition getSplitSlideNotesPosition() {
return splitSlideNotesPosition;
}
/**
* Sets the position of the slide notes
*
* @param position a position that doesn't depend on prediction
*/
public void setSplitSlideNotesPosition(NotesPosition position) {
this.splitSlideNotesPosition = position;
}
/**
* Get the position of the notes when the slide needs to be split on exports
*
* @return position of the notes on the slide
*/
public NotesPosition getActualSplitSlideNotesPosition() {
return actualSplitSlideNotesPosition;
}
/**
* Sets the position of the slide notes for PDF exports
*
* @param position a position that doesn't depend on prediction
*/
public void setActualSplitSlideNotesPosition(NotesPosition position) {
this.actualSplitSlideNotesPosition = position;
}
}

View file

@ -0,0 +1,30 @@
/*
*
* * Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* * Embedded Systems and Applications Group.
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.lecturestudio.core.model;
public enum NotesPosition {
LEFT,
RIGHT,
NONE,
UNKNOWN
}

View file

@ -95,6 +95,8 @@ public class Page {
/** The unique ID of this document. */
private UUID uid;
private boolean overlay = false;
/**
* Create a new Page with the specified document and page number.
@ -659,4 +661,11 @@ public class Page {
return annotationsAsString;
}
public boolean isOverlay() {
return overlay;
}
public void setOverlay(boolean overlay) {
this.overlay = overlay;
}
}

View file

@ -81,7 +81,7 @@ public class ScreenDocument extends Document {
int padding = (int) (PADDING * s);
PDFGraphics2D g2d = (PDFGraphics2D) getPdfDocument().createAppendablePageGraphics2D(pageIndex);
PDFGraphics2D g2d = (PDFGraphics2D) getPdfDocument().createAppendablePageGraphics2D(pageIndex, getSplitSlideNotesPosition());
// Draw screen frame with a border around it.
g2d.setColor(Color.BLACK);
g2d.drawRect(x - padding, y - padding, w + padding * 2, h + padding * 2);

View file

@ -29,6 +29,7 @@ import java.util.Set;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.DocumentOutline;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.shape.Shape;
public interface DocumentAdapter {
@ -63,6 +64,18 @@ public interface DocumentAdapter {
*/
Graphics2D createGraphics(int pageIndex, String name, boolean appendContent);
/**
* Create a {@link Graphics2D} object with the specified parameters.
*
* @param pageIndex The index of the PDF page to which to draw.
* @param name The PDF graphics stream name.
* @param appendContent {@code true} if content should be appended to the existing one, {@code false} to overwrite.
* @param notesPosition The position of split slides notes
*
* @return The newly created {@link Graphics2D} object.
*/
Graphics2D createGraphics(int pageIndex, String name, boolean appendContent, NotesPosition notesPosition);
/**
* Set the title of the document.
*
@ -95,10 +108,11 @@ public interface DocumentAdapter {
* Get the bounds of the page that has the specified page number.
*
* @param pageNumber The page number.
* @param position The position of notes on a page.
*
* @return The bounds of the page that has the specified page number.
*/
Rectangle2D getPageBounds(int pageNumber);
Rectangle2D getPageBounds(int pageNumber, NotesPosition position);
/**
* Get the number of pages in the document.
@ -120,10 +134,11 @@ public interface DocumentAdapter {
* Get the word bounds of the page that has the specified page number.
*
* @param pageNumber The page number.
* @param splitNotesPosition Position of the slide notes
*
* @return The word bounds of the page that has the specified page number.
*/
List<Rectangle2D> getPageWordsNormalized(int pageNumber) throws IOException;
List<Rectangle2D> getPageWordsNormalized(int pageNumber, NotesPosition splitNotesPosition) throws IOException;
/**
* Get the text bounds of the page that has the specified page number.

View file

@ -28,4 +28,6 @@ public interface DocumentRenderer {
void render(Page page, PresentationParameter parameter, BufferedImage image) throws IOException;
void renderNotes(Page page, PresentationParameter parameter, BufferedImage image) throws IOException;
}

View file

@ -28,6 +28,7 @@ import java.util.Map;
import org.lecturestudio.core.geometry.Dimension2D;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.DocumentOutline;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.shape.Shape;
import org.lecturestudio.core.pdf.mupdf.MuPDFDocument;
import org.lecturestudio.core.pdf.pdfbox.PDFBoxDocument;
@ -156,6 +157,29 @@ public class PdfDocument {
muPDFDocument.deletePage(pageNumber);
}
/**
* Get the bounds of the page that has the specified page index.
*
* @param pageIndex The page index.
* @param position The position of notes on a page.
*
* @return The bounds of the page that has the specified page index.
*/
public Rectangle2D getPageMediaBox(int pageIndex, NotesPosition position) {
return pdfBoxDocument.getPageBounds(pageIndex, position);
}
/**
* Get the bounds of the page that has the specified page index.
*
* @param pageIndex The page index.
*
* @return The bounds of the page that has the specified page index.
*/
public Rectangle2D getPageBounds(int pageIndex) {
return muPDFDocument.getPageBounds(pageIndex, NotesPosition.NONE);
}
/**
* Get the bounds of the page that has the specified page index.
*
@ -164,7 +188,7 @@ public class PdfDocument {
* @return The bounds of the page that has the specified page index.
*/
public Rectangle2D getPageMediaBox(int pageIndex) {
return pdfBoxDocument.getPageBounds(pageIndex);
return getPageMediaBox(pageIndex, NotesPosition.UNKNOWN);
}
public void setPageContentTransform(int pageIndex, AffineTransform transform) throws IOException {
@ -177,6 +201,9 @@ public class PdfDocument {
return pdfBoxDocument.importPage(pdfDocument.pdfBoxDocument, pageIndex, pageRect);
}
public void setCropbox(int pageNumber,int x, int y, int width, int height){
pdfBoxDocument.setCropbox(pageNumber,x , y, width,height);
}
/**
* Replaces the page that has the {@code pageIndex} with the page
* that has {@code docIndex} in {@code newPdfDocument}.
@ -212,11 +239,12 @@ public class PdfDocument {
* The content will be appended to the existing one.
*
* @param pageIndex The index of the page to which to draw.
* @param notesPosition The position of split slides notes
*
* @return The newly created {@link Graphics2D} object.
*/
public Graphics2D createAppendablePageGraphics2D(int pageIndex) {
return pdfBoxDocument.createGraphics(pageIndex, null, true);
public Graphics2D createAppendablePageGraphics2D(int pageIndex, NotesPosition notesPosition) {
return pdfBoxDocument.createGraphics(pageIndex, null, true, notesPosition);
}
/**
@ -225,11 +253,12 @@ public class PdfDocument {
*
* @param pageIndex The index of the page to which to draw.
* @param name The PDF graphics stream name.
*
* @param notesPosition The position of split slides notes
* @return The newly created {@link Graphics2D} object.
*/
public Graphics2D createAppendablePageGraphics2D(int pageIndex, String name) {
return pdfBoxDocument.createGraphics(pageIndex, name, true);
public Graphics2D createAppendablePageGraphics2D(int pageIndex, String name, NotesPosition notesPosition) {
return pdfBoxDocument.createGraphics(pageIndex, name, true, notesPosition);
}
/**
@ -294,8 +323,8 @@ public class PdfDocument {
* @param pageNumber The page number.
* @return The word bounds of the page that has the specified page number.
*/
public List<Rectangle2D> getNormalizedWordPositions(int pageNumber) {
return muPDFDocument.getPageWordsNormalized(pageNumber);
public List<Rectangle2D> getNormalizedWordPositions(int pageNumber, NotesPosition splitNotesPosition) {
return muPDFDocument.getPageWordsNormalized(pageNumber, splitNotesPosition);
}
/**

View file

@ -54,6 +54,7 @@ import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.io.BitConverter;
import org.lecturestudio.core.model.DocumentOutline;
import org.lecturestudio.core.model.DocumentOutlineItem;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.shape.Shape;
import org.lecturestudio.core.pdf.DocumentAdapter;
import org.lecturestudio.core.pdf.DocumentRenderer;
@ -142,6 +143,11 @@ public class MuPDFDocument implements DocumentAdapter {
return null;
}
@Override
public Graphics2D createGraphics(int pageIndex, String name, boolean appendContent, NotesPosition notesPosition) {
return null;
}
@Override
public void setTitle(String title) {
//doc.setTitle(title);
@ -167,12 +173,17 @@ public class MuPDFDocument implements DocumentAdapter {
}
@Override
public Rectangle2D getPageBounds(int pageNumber) {
public Rectangle2D getPageBounds(int pageNumber, NotesPosition position) {
synchronized (mutex) {
Page page = getPage(pageNumber);
Rect bounds = page.getBounds();
try {
Page page = getPage(pageNumber);
Rect bounds = page.getBounds();
return new Rectangle2D(0, 0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
return new Rectangle2D(0, 0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
}
catch (Throwable e) {
return new Rectangle2D();
}
}
}
@ -199,12 +210,12 @@ public class MuPDFDocument implements DocumentAdapter {
}
@Override
public List<Rectangle2D> getPageWordsNormalized(int pageNumber) {
public List<Rectangle2D> getPageWordsNormalized(int pageNumber, NotesPosition splitNotesPosition) {
synchronized (mutex) {
DisplayList displayList = getDisplayList(pageNumber);
Page page = getPage(pageNumber);
WordWalker wordWalker = new WordWalker(page.getBounds());
WordWalker wordWalker = new WordWalker(page.getBounds(), splitNotesPosition);
StructuredText structuredText = displayList.toStructuredText();
structuredText.walk(wordWalker);

View file

@ -26,7 +26,7 @@ import com.artifex.mupdf.fitz.Pixmap;
import com.artifex.mupdf.fitz.Rect;
import com.artifex.mupdf.fitz.RectI;
import java.awt.Graphics2D;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
@ -36,6 +36,7 @@ import java.util.Map;
import org.lecturestudio.core.geometry.Point2D;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.pdf.DocumentRenderer;
import org.lecturestudio.core.view.PresentationParameter;
@ -68,6 +69,9 @@ public class MuPDFRenderer implements DocumentRenderer {
synchronized (lock) {
Rectangle2D pageRect = parameter.getViewRect();
int pageNumber = page.getPageNumber();
//Needed for notes on right side
float stmX = 0;
float ctmX = 0;
double sx = imageWidth / pageRect.getWidth();
double sy = imageHeight / pageRect.getHeight();
@ -80,19 +84,101 @@ public class MuPDFRenderer implements DocumentRenderer {
com.artifex.mupdf.fitz.Page p = document.getPage(pageNumber);
Rect bounds = p.getBounds();
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.RIGHT) {
bounds.x1 = bounds.x1 / 2;
}
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.LEFT) {
bounds.x0 = bounds.x1 / 2;
x = (int) (x - (imageWidth - sx));
}
float scale = (float) (1.D / pageRect.getWidth());
float pageSx = imageWidth / (bounds.x1 - bounds.x0);
float pageSy = imageHeight / (bounds.y1 - bounds.y0);
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.LEFT) {
stmX = bounds.x0 * pageSx;
ctmX = bounds.x0 * pageSx;
}
Matrix ctm = new Matrix();
ctm.translate(-x, -y);
//ctm.translate(-x, -y);
ctm.translate(-x - ctmX, -y);
ctm.scale(pageSx * scale, pageSy * scale);
int px = (int) (pageRect.getX() * pageSx);
int py = (int) (pageRect.getY() * pageSy);
Matrix stm = new Matrix();
stm.translate(-px, -py);
//stm.translate(-px, -py);
stm.translate(-px - stmX, -py);
stm.scale(pageSx, pageSy);
if (parameter.isTranslation()) {
renderPan(parameter, image, displayList, bounds, ctm, stm);
}
else {
RectI scissor = new RectI(bounds).transform(stm);
Rect pixmapBounds = new Rect(0, 0, imageWidth, imageHeight);
renderImage(image, displayList, pixmapBounds, ctm, scissor);
sizeMap.put(imageWidth, new Point2D(x, y));
}
}
}
@Override
public void renderNotes(Page page, PresentationParameter parameter,
BufferedImage image) throws IOException {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
synchronized (lock) {
Rectangle2D pageRect = parameter.getViewRect();
int pageNumber = page.getPageNumber();
//Needed for notes on right side
float stmX = 0;
float ctmX = 0;
double sx = imageWidth / pageRect.getWidth();
double sy = imageHeight / pageRect.getHeight();
int x = (int) (pageRect.getX() * sx);
int y = (int) (pageRect.getY() * sy);
DisplayList displayList = document.getDisplayList(pageNumber);
com.artifex.mupdf.fitz.Page p = document.getPage(pageNumber);
Rect bounds = p.getBounds();
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.LEFT) {
bounds.x1 = bounds.x1 / 2;
}
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.RIGHT) {
bounds.x0 = bounds.x1 / 2;
}
float scale = (float) (1.D / pageRect.getWidth());
float pageSx = imageWidth / (bounds.x1 - bounds.x0);
float pageSy = imageHeight / (bounds.y1 - bounds.y0);
if (page.getDocument().getSplitSlideNotesPosition() == NotesPosition.RIGHT) {
stmX = bounds.x0 * pageSx;
ctmX = bounds.x0 * pageSx;
}
Matrix ctm = new Matrix();
//ctm.translate(-x, -y);
ctm.translate(-x - ctmX, -y);
ctm.scale(pageSx * scale, pageSy * scale);
int px = (int) (pageRect.getX() * pageSx);
int py = (int) (pageRect.getY() * pageSy);
Matrix stm = new Matrix();
//stm.translate(-px, -py);
stm.translate(-px - stmX, -py);
stm.scale(pageSx, pageSy);
if (parameter.isTranslation()) {

View file

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.NotesPosition;
public class WordWalker implements StructuredTextWalker {
@ -39,13 +40,15 @@ public class WordWalker implements StructuredTextWalker {
private boolean inBounds = false;
private NotesPosition splitNotesPosition;
/**
* Create a new {@link WordWalker} with the specified page bounds.
*
* @param pageBounds The page bounds.
*/
public WordWalker(Rect pageBounds) {
public WordWalker(Rect pageBounds, NotesPosition splitNotesPosition) {
this.splitNotesPosition = splitNotesPosition;
this.pageBounds = pageBounds;
this.scale = 1.F / (pageBounds.x1 - pageBounds.x0);
this.boundsList = new ArrayList<>();
@ -103,12 +106,28 @@ public class WordWalker implements StructuredTextWalker {
}
private void saveWord(Rect wordBounds) {
double x = scale * wordBounds.x0;
double y = scale * wordBounds.y0;
double w = scale * (wordBounds.x1 - wordBounds.x0);
double h = scale * (wordBounds.y1 - wordBounds.y0);
double x = 0;
double y = 0;
double w = 0;
double h = 0;
if(splitNotesPosition == NotesPosition.LEFT){
x = scale*2 * (wordBounds.x0 - pageBounds.x1/2);
y = scale*2 * (wordBounds.y0);
w = scale*2 * (wordBounds.x1 - wordBounds.x0);
h = scale*2 * (wordBounds.y1 - wordBounds.y0);
}else if(splitNotesPosition == NotesPosition.RIGHT){
x = scale*2 * wordBounds.x0;
y = scale*2 * wordBounds.y0;
w = scale*2 * (wordBounds.x1 - wordBounds.x0);
h = scale*2 * (wordBounds.y1 - wordBounds.y0);
}else{
x = scale * wordBounds.x0 ;
y = scale * wordBounds.y0;
w = scale * (wordBounds.x1 - wordBounds.x0);
h = scale * (wordBounds.y1 - wordBounds.y0);
}
boundsList.add(new Rectangle2D(x, y, w, h));
}
}

View file

@ -64,6 +64,7 @@ import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.io.BitConverter;
import org.lecturestudio.core.model.DocumentOutline;
import org.lecturestudio.core.model.DocumentOutlineItem;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.shape.Shape;
import org.lecturestudio.core.pdf.DocumentAdapter;
import org.lecturestudio.core.pdf.DocumentRenderer;
@ -153,8 +154,20 @@ public class PDFBoxDocument implements DocumentAdapter {
@Override
public PDFGraphics2D createGraphics(int pageIndex, String name, boolean appendContent) {
PDPage pdPage = doc.getPage(pageIndex);
return createGraphics(pageIndex, name, appendContent, NotesPosition.NONE);
}
@Override
public PDFGraphics2D createGraphics(int pageIndex, String name, boolean appendContent, NotesPosition notesPosition) {
PDPage pdPage = doc.getPage(pageIndex);
if(notesPosition == NotesPosition.LEFT){
PDRectangle rect = pdPage.getMediaBox();
pdPage.setCropBox(new PDRectangle(rect.getWidth()/2, 0, rect.getWidth()/2, rect.getHeight()));
}
if(notesPosition == NotesPosition.RIGHT){
PDRectangle rect = pdPage.getMediaBox();
pdPage.setCropBox(new PDRectangle(0, 0, rect.getWidth()/2, rect.getHeight()));
}
return new PDFGraphics2D(doc, pdPage, name, appendContent);
}
@ -179,10 +192,20 @@ public class PDFBoxDocument implements DocumentAdapter {
}
@Override
public Rectangle2D getPageBounds(int pageNumber) {
public Rectangle2D getPageBounds(int pageNumber, NotesPosition position) {
PDPage page = doc.getPage(pageNumber);
PDRectangle rect = page.getMediaBox();
if (position == NotesPosition.UNKNOWN) {
position = rect.getWidth() / rect.getHeight() >= 2 ? NotesPosition.RIGHT : NotesPosition.UNKNOWN;
}
if (position == NotesPosition.RIGHT) {
return new Rectangle2D(0, 0, rect.getWidth() / 2, rect.getHeight());
}
if (position == NotesPosition.LEFT) {
return new Rectangle2D(rect.getWidth() / 2, 0, rect.getWidth() / 2, rect.getHeight());
}
return new Rectangle2D(0, 0, rect.getWidth(), rect.getHeight());
}
@ -199,7 +222,7 @@ public class PDFBoxDocument implements DocumentAdapter {
}
@Override
public List<Rectangle2D> getPageWordsNormalized(int pageNumber) throws IOException {
public List<Rectangle2D> getPageWordsNormalized(int pageNumber, NotesPosition splitNotesPosition) throws IOException {
WordBoundsExtractor wordExtractor = new WordBoundsExtractor(doc);
return wordExtractor.getWordBounds(pageNumber + 1);
@ -351,6 +374,11 @@ public class PDFBoxDocument implements DocumentAdapter {
page.setContents(newContents);
}
public void setCropbox(int pageNumber, int x, int y, int width, int height){
PDPage page = doc.getPage(pageNumber);
page.setCropBox(new PDRectangle(x, y, width, height));
}
public synchronized int importPage(PDFBoxDocument srcDocument, int pageNumber, AffineTransform transform) throws IOException {
PDDocument sourceDocument = srcDocument.doc;
@ -359,6 +387,7 @@ public class PDFBoxDocument implements DocumentAdapter {
PDPage imported = doc.importPage(page);
imported.setResources(page.getResources());
imported.setMediaBox(imported.getCropBox());
if (page.getRotation() == 90) {
// Set rotation to zero.

View file

@ -81,4 +81,9 @@ public class PDFBoxRenderer implements DocumentRenderer {
g.dispose();
}
@Override
public void renderNotes(Page page, PresentationParameter parameter, BufferedImage image) throws IOException{
}
}

View file

@ -256,6 +256,7 @@ public class DocumentRecorder extends ExecutableBase {
}
}
}
recDocument.setActualSplitSlideNotesPosition(pageDoc.getSplitSlideNotesPosition());
try {
PresentationParameter param = paramProvider.getParameter(page);

View file

@ -42,12 +42,7 @@ import org.lecturestudio.core.bus.event.DocumentEvent;
import org.lecturestudio.core.bus.event.PageEvent;
import org.lecturestudio.core.geometry.Dimension2D;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.DocumentList;
import org.lecturestudio.core.model.DocumentType;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.model.RecentDocument;
import org.lecturestudio.core.model.TemplateDocument;
import org.lecturestudio.core.model.*;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.core.view.PresentationParameterProvider;
import org.lecturestudio.core.view.ViewType;
@ -192,7 +187,6 @@ public class DocumentService {
selectDocument(doc);
updateRecentDocuments(doc);
return doc;
});
}
@ -407,6 +401,22 @@ public class DocumentService {
}
}
public void selectNotesPosition(NotesPosition pos) {
Document doc = documents.getSelectedDocument();
if (nonNull(doc)) {
if (!doc.hasNoteSlide()) {
return;
}
doc.setSplitSlideNotesPosition(pos);
doc.calculateCropBox();
}
context.getEventBus().post(new PageEvent(doc.getCurrentPage(),
PageEvent.Type.SELECTED));
}
private void selectPage(Document document, int pageNumber) {
int currentPageNumber = document.getCurrentPageNumber();

View file

@ -40,6 +40,7 @@ public enum ToolType {
ELLIPSE,
SELECT,
SELECT_GROUP,
CLONE
CLONE,
BOOKMARK
}

View file

@ -20,6 +20,6 @@ package org.lecturestudio.core.view;
public enum ViewType {
Preview, User, Presentation
Preview, User, Presentation, Slide_Notes
}

View file

@ -200,7 +200,7 @@ public class PageEventsPresenter extends Presenter<PageEventsView> {
|| !previousAction.hasHandle()
|| action.getHandle() != previousAction.getHandle()) {
if (nonNull(previousAction) && nonNull(previousEndTs)
&& Math.abs(action.getTimestamp() - previousEndTs) < 1000
&& Math.abs(action.getTimestamp() - previousEndTs) < -1000
&& action.getClass().equals(previousAction.getClass())) {
// This is a composite action.
PageEvent initEvent = eventList.get(eventList.size() - 1);

View file

@ -1947,7 +1947,7 @@
"angular2-template-loader": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz",
"integrity": "sha512-jBSrm2yDsTA48GG0H57upn8rmTfJS3/R7EhUeAL/3ryWS8deT9You8UQKWpW4eVSEY7uMJ52iFwpOYXc8QEtGg==",
"integrity": "sha1-wNROkP/w+sleiyPwQ6zaf9HFHXw=",
"dev": true,
"requires": {
"loader-utils": "^0.2.15"
@ -1962,7 +1962,7 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": {
@ -2114,7 +2114,7 @@
"batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
"dev": true
},
"big.js": {
@ -2181,7 +2181,7 @@
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"bootstrap": {
@ -2400,7 +2400,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"color-string": {
@ -2428,7 +2428,7 @@
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"compressible": {
@ -2458,7 +2458,7 @@
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
"dev": true
},
"debug": {
@ -2473,7 +2473,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"safe-buffer": {
@ -2487,7 +2487,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concurrently": {
@ -2604,7 +2604,7 @@
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
"core-js-compat": {
@ -2649,7 +2649,7 @@
"css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
"integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==",
"integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
"dev": true
},
"css-loader": {
@ -2851,7 +2851,7 @@
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
"integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
"dev": true
},
"dns-packet": {
@ -2927,7 +2927,7 @@
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"electron-to-chromium": {
@ -2945,13 +2945,13 @@
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"integrity": "sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng==",
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
"dev": true
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true
},
"enhanced-resolve": {
@ -3077,13 +3077,13 @@
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eslint-scope": {
@ -3128,7 +3128,7 @@
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
"eventemitter3": {
@ -3374,7 +3374,7 @@
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
"dev": true
},
"fs-extra": {
@ -3403,7 +3403,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
@ -3559,7 +3559,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"has-property-descriptors": {
@ -3607,7 +3607,7 @@
"hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
"integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
"integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
"dev": true,
"requires": {
"inherits": "^2.0.1",
@ -3651,13 +3651,13 @@
"hsl-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
"integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==",
"integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=",
"dev": true
},
"hsla-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
"integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==",
"integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=",
"dev": true
},
"html-entities": {
@ -3753,7 +3753,7 @@
"http-deceiver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
"integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
"integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
"dev": true
},
"http-errors": {
@ -3849,7 +3849,7 @@
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
@ -3899,7 +3899,7 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-bigint": {
@ -3939,7 +3939,7 @@
"is-color-stop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz",
"integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==",
"integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=",
"dev": true,
"requires": {
"css-color-names": "^0.0.4",
@ -3977,7 +3977,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": {
@ -4108,19 +4108,19 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"jest-worker": {
@ -4224,7 +4224,7 @@
"loader-utils": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
"integrity": "sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug==",
"integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
"dev": true,
"requires": {
"big.js": "^3.1.3",
@ -4236,7 +4236,7 @@
"json5": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
}
}
@ -4259,13 +4259,13 @@
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
"lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==",
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=",
"dev": true
},
"lower-case": {
@ -4307,7 +4307,7 @@
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
"memfs": {
@ -4322,7 +4322,7 @@
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
"dev": true
},
"merge-stream": {
@ -4340,7 +4340,7 @@
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"dev": true
},
"micromatch": {
@ -4491,7 +4491,7 @@
"normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
"dev": true
},
"npm-run-path": {
@ -4515,7 +4515,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"object-hash": {
@ -4584,7 +4584,7 @@
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
@ -4700,7 +4700,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
@ -4718,7 +4718,7 @@
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
"path-type": {
@ -4904,7 +4904,7 @@
"pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
"integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
"dev": true
},
"process-nextick-args": {
@ -5127,7 +5127,7 @@
"relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
"dev": true
},
"renderkid": {
@ -5146,7 +5146,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-from-string": {
@ -5158,7 +5158,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"resize-observer-polyfill": {
@ -5216,13 +5216,13 @@
"rgb-regex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
"integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==",
"integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=",
"dev": true
},
"rgba-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
"integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==",
"integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=",
"dev": true
},
"rimraf": {
@ -5381,7 +5381,7 @@
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
"integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
"dev": true
},
"selfsigned": {
@ -5457,7 +5457,7 @@
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
"integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
"integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
"dev": true,
"requires": {
"accepts": "~1.3.4",
@ -5487,7 +5487,7 @@
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true,
"requires": {
"depd": "~1.1.2",
@ -5499,13 +5499,13 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"setprototypeof": {
@ -5584,7 +5584,7 @@
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
"dev": true,
"requires": {
"is-arrayish": "^0.3.1"
@ -5640,7 +5640,7 @@
"spawn-command": {
"version": "0.0.2-1",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
"integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==",
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
"dev": true
},
"spdy": {
@ -5738,7 +5738,7 @@
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
@ -6015,7 +6015,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-regex-range": {
@ -6164,7 +6164,7 @@
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true
},
"update-browserslist-db": {
@ -6189,7 +6189,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"util.promisify": {
@ -6205,13 +6205,13 @@
"utila": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
"integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
"integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
"dev": true
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"dev": true
},
"uuid": {
@ -6223,7 +6223,7 @@
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"dev": true
},
"watchpack": {
@ -6652,7 +6652,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"ws": {

View file

@ -35,6 +35,7 @@ import org.lecturestudio.core.text.TeXFont;
import org.lecturestudio.core.text.TextAttributes;
import org.lecturestudio.core.tool.PresetColor;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.ParticipantsPosition;
import org.lecturestudio.presenter.api.net.ScreenShareProfiles;
public class DefaultConfiguration extends PresenterConfiguration {
@ -105,7 +106,7 @@ public class DefaultConfiguration extends PresenterConfiguration {
getSlideViewConfiguration().setLeftSliderPosition(0.375);
getSlideViewConfiguration().setRightSliderPosition(0.8);
getSlideViewConfiguration().setMessageBarPosition(MessageBarPosition.BOTTOM);
getSlideViewConfiguration().setParticipantsPosition(MessageBarPosition.LEFT);
getSlideViewConfiguration().setParticipantsPosition(ParticipantsPosition.LEFT);
AudioProcessingSettings processingSettings = new AudioProcessingSettings();
processingSettings.setHighpassFilterEnabled(true);

View file

@ -45,6 +45,8 @@ public class PresenterConfiguration extends Configuration {
private final ExternalWindowConfiguration externalSlidePreviewConfig = new ExternalWindowConfiguration();
private final ExternalWindowConfiguration externalSlideNotesConfig = new ExternalWindowConfiguration();
private final ExternalWindowConfiguration externalSpeechConfig = new ExternalWindowConfiguration();
private final ExternalWindowConfiguration externalNotesConfig = new ExternalWindowConfiguration();
@ -177,4 +179,12 @@ public class PresenterConfiguration extends Configuration {
public SlideViewConfiguration getSlideViewConfiguration() {
return slideViewConfiguration;
}
/**
* @return External slide notes configuration
*/
public ExternalWindowConfiguration getExternalSlideNotesConfig() {
return externalSlideNotesConfig;
}
}

View file

@ -20,22 +20,27 @@ package org.lecturestudio.presenter.api.config;
import org.lecturestudio.core.beans.DoubleProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.model.*;
public class SlideViewConfiguration {
private final ObjectProperty<MessageBarPosition> messageBarPosition = new ObjectProperty<>(
MessageBarPosition.BOTTOM);
private final ObjectProperty<NoteBarPosition> notesBarPosition = new ObjectProperty<>(
NoteBarPosition.BOTTOM);
private final ObjectProperty<SlideNotesPosition> slideNotesPosition = new ObjectProperty<>(
SlideNotesPosition.BOTTOM);
private final ObjectProperty<MessageBarPosition> participantsPosition = new ObjectProperty<>(
MessageBarPosition.LEFT);
private final ObjectProperty<ParticipantsPosition> participantsPosition = new ObjectProperty<>(
ParticipantsPosition.LEFT);
private final ObjectProperty<MessageBarPosition> previewPosition = new ObjectProperty<>(
MessageBarPosition.RIGHT);
private final ObjectProperty<SlidePreviewPosition> previewPosition = new ObjectProperty<>(
SlidePreviewPosition.RIGHT);
private final ObjectProperty<NoteSlidePosition> noteSlidePosition = new ObjectProperty<>(
NoteSlidePosition.NONE);
private final ObjectProperty<SpeechPosition> speechPosition = new ObjectProperty<>(
SpeechPosition.ABOVE_SLIDE_PREVIEW);
private final DoubleProperty leftSliderPosition = new DoubleProperty(0.375);
@ -97,41 +102,70 @@ public class SlideViewConfiguration {
/**
* @return Notes bar's position
*/
public NoteBarPosition getNotesBarPosition() {
return notesBarPosition.get();
public SlideNotesPosition getSlideNotesPosition() {
return slideNotesPosition.get();
}
/**
* @param position Notes bar's position
*/
public void setNotesBarPosition(NoteBarPosition position) {
notesBarPosition.set(position);
public void setSlideNotesPosition(SlideNotesPosition position) {
slideNotesPosition.set(position);
}
public MessageBarPosition getParticipantsPosition() {
public ParticipantsPosition getParticipantsPosition() {
return participantsPosition.get();
}
public void setParticipantsPosition(MessageBarPosition position) {
public void setParticipantsPosition(ParticipantsPosition position) {
participantsPosition.set(position);
}
/**
* @param position Slide preview position
*/
public void setPreviewPosition(MessageBarPosition position) {
public void setSlidePreviewPosition(SlidePreviewPosition position) {
previewPosition.set(position);
}
/**
* @return Slide preview position
*/
public MessageBarPosition getPreviewPosition() {
public SlidePreviewPosition getSlidePreviewPosition() {
return previewPosition.get();
}
public ObjectProperty<MessageBarPosition> previewPositionProperty() {
public ObjectProperty<SlidePreviewPosition> slidePreviewPositionProperty() {
return previewPosition;
}
/**
* @return Slide notes bar's position
*/
public NoteSlidePosition getNoteSlidePosition() {
return noteSlidePosition.get();
}
/**
* @param position Slide notes bar's position
*/
public void setNoteSlidePosition(NoteSlidePosition position) {
noteSlidePosition.set(position);
}
/**
* @return the speech position
*/
public SpeechPosition getSpeechPosition() {
return speechPosition.get();
}
/**
* @param position The speech position
*/
public void setSpeechPosition(SpeechPosition position) {
speechPosition.set(position);
}
}

View file

@ -1,30 +1,29 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* * Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* * Embedded Systems and Applications Group.
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.event;
/**
* Handles events for external notes views
*
* @author Dustin Ringel
*/
public class ExternalNotesViewEvent extends ExternalViewEvent {
public class ExternalNotesViewEvent extends ExternalViewEvent {
/**
* Creates a new {@code ExternalNotesViewEvent} with the provided parameters.
*

View file

@ -0,0 +1,48 @@
/*
*
* * Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* * Embedded Systems and Applications Group.
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.lecturestudio.presenter.api.event;
/**
* Handles events for external slide notes views
*
* @author Dustin Ringel
*/
public class ExternalSlideNotesViewEvent extends ExternalViewEvent {
/**
* Creates a new {@code ExternalSlideNotesViewEvent} with the provided parameters.
*
* @param enabled Enable the MenuItem
*/
public ExternalSlideNotesViewEvent(boolean enabled) {
super(enabled);
}
/**
* Creates a new {@code ExternalSlideNotesViewEvent} with the provided parameters.
*
* @param enabled Enable the MenuItem
* @param show Open the external notesViewWindow
*/
public ExternalSlideNotesViewEvent(boolean enabled, boolean show) {
super(enabled, show);
}
}

View file

@ -20,9 +20,6 @@ package org.lecturestudio.presenter.api.event;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
public class MessageBarPositionEvent extends PositionEvent {
public record MessageBarPositionEvent(MessageBarPosition position) {
public MessageBarPositionEvent(MessageBarPosition position) {
super(position);
}
}

View file

@ -20,22 +20,13 @@
package org.lecturestudio.presenter.api.event;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.model.SlideNotesPosition;
/**
* Handles the position for Notes in a tabbar
* Handles the position for Notes in a tab-bar.
*
* @author Dustin Ringel
*/
public class NotesBarPositionEvent extends PositionEvent {
/**
* Creates a new {@code NotesBarPositionEvent} with the provided parameters.
*
* @param position Position of the notes tab
*/
public NotesBarPositionEvent(NoteBarPosition position) {
super(position);
}
public record NotesBarPositionEvent(SlideNotesPosition position) {
}

View file

@ -18,11 +18,8 @@
package org.lecturestudio.presenter.api.event;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.ParticipantsPosition;
public class ParticipantsPositionEvent extends PositionEvent {
public record ParticipantsPositionEvent(ParticipantsPosition position) {
public ParticipantsPositionEvent(MessageBarPosition position) {
super(position);
}
}

View file

@ -18,11 +18,8 @@
package org.lecturestudio.presenter.api.event;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.SlidePreviewPosition;
public class PreviewPositionEvent extends PositionEvent {
public record PreviewPositionEvent(SlidePreviewPosition position) {
public PreviewPositionEvent(MessageBarPosition position) {
super(position);
}
}

View file

@ -18,29 +18,8 @@
package org.lecturestudio.presenter.api.event;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.model.NoteSlidePosition;
public abstract class PositionEvent {
public record SlideNotesBarPositionEvent(NoteSlidePosition position) {
private MessageBarPosition messageBarPosition;
private NoteBarPosition noteBarPosition;
public PositionEvent(MessageBarPosition messageBarPosition) {
this.messageBarPosition = messageBarPosition;
}
public PositionEvent(NoteBarPosition noteBarPosition){
this.noteBarPosition = noteBarPosition;
}
public MessageBarPosition getMessageBarPosition() {
return messageBarPosition;
}
public NoteBarPosition getNoteBarPosition(){
return noteBarPosition;
}
}

View file

@ -30,20 +30,30 @@ public enum Shortcut {
DOC_OPEN (KeyCode.O, KeyEvent.CTRL_MASK),
DOC_CLOSE (KeyCode.F4, KeyEvent.CTRL_MASK),
SLIDE_FIRST (KeyCode.HOME),
SLIDE_LAST (KeyCode.END),
SLIDE_NEXT_RIGHT (KeyCode.RIGHT),
SLIDE_NEXT_DOWN (KeyCode.DOWN),
SLIDE_NEXT_PAGE_DOWN (KeyCode.PAGE_DOWN),
SLIDE_NEXT_SPACE (KeyCode.SPACE),
SLIDE_NEXT_10 (KeyCode.RIGHT, KeyEvent.SHIFT_MASK),
SLIDE_PREVIOUS_LEFT (KeyCode.LEFT),
SLIDE_PREVIOUS_UP (KeyCode.UP),
SLIDE_PREVIOUS_BACK_SPACE (KeyCode.BACK_SPACE),
SLIDE_PREVIOUS_PAGE_UP (KeyCode.PAGE_UP),
SLIDE_PREVIOUS_10 (KeyCode.LEFT, KeyEvent.SHIFT_MASK),
SLIDE_NEW (KeyCode.F9),
SLIDE_DELETE (KeyCode.D, KeyEvent.CTRL_MASK),
SLIDE_PAN (KeyCode.F11),
SLIDE_OVERLAY_START (KeyCode.UP, KeyEvent.SHIFT_MASK),
SLIDE_OVERLAY_END (KeyCode.DOWN, KeyEvent.SHIFT_MASK),
SLIDE_OVERLAY_PREVIOUS (KeyCode.PAGE_UP, KeyEvent.SHIFT_MASK),
SLIDE_OVERLAY_NEXT (KeyCode.PAGE_DOWN, KeyEvent.SHIFT_MASK),
COPY_OVERLAY (KeyCode.V, KeyEvent.CTRL_MASK),
COPY_OVERLAY_NEXT_PAGE_CTRL (KeyCode.PAGE_DOWN, KeyEvent.CTRL_MASK),
COPY_OVERLAY_NEXT_PAGE_SHIFT (KeyCode.PAGE_DOWN, KeyEvent.SHIFT_MASK),
UNDO (KeyCode.Z, KeyEvent.CTRL_MASK),
REDO (KeyCode.Y, KeyEvent.CTRL_MASK),
@ -70,6 +80,12 @@ public enum Shortcut {
BOOKMARK_NEW (KeyCode.B),
BOOKMARK_GOTO (KeyCode.G),
BOOKMARK_SLIDE (KeyCode.M),
BOOKMARK_GOTO_LAST (KeyCode.M, KeyEvent.SHIFT_MASK),
TIMER_START (KeyCode.S),
TIMER_PAUSE (KeyCode.PAUSE),
TIMER_RESET (KeyCode.T, KeyEvent.CTRL_MASK),
COLOR_CUSTOM (KeyCode.F1),
COLOR_1 (KeyCode.F2),

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public class BookmarkExistsException extends BookmarkException {
/**
* Constructs a new BookmarkExistsException with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message.
*/
public BookmarkExistsException(String message) {
super(message);
}
}

View file

@ -71,6 +71,16 @@ public class Bookmarks {
return bookmarkList;
}
public Bookmark getLastBookmark(Document doc) {
List<Bookmark> docBookmarks = bookmarks.get(doc);
if (isNull(docBookmarks) || docBookmarks.isEmpty()) {
return null;
}
return docBookmarks.get(docBookmarks.size() - 1);
}
public List<Bookmark> getDocumentBookmarks(Document doc) {
return bookmarks.get(doc);
}

View file

@ -1,7 +1,28 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum MessageBarPosition {
BOTTOM,
LEFT,
RIGHT
RIGHT,
EXTERNAL
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum NoteSlidePosition {
BELOW_SLIDE_PREVIEW,
NONE,
EXTERNAL
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum ParticipantsPosition {
LEFT,
RIGHT,
EXTERNAL
}

View file

@ -20,12 +20,7 @@
package org.lecturestudio.presenter.api.model;
/**
* Positions where the note tab can appear
*
* @author Dustin Ringel
*/
public enum NoteBarPosition {
BOTTOM,
LEFT
public enum SlideNoteBarPosition {
BELOW_PREVIEW, NONE
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum SlideNotesPosition {
BOTTOM,
LEFT,
RIGHT,
EXTERNAL
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum SlidePreviewPosition {
LEFT,
RIGHT,
EXTERNAL
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.model;
public enum SpeechPosition {
ABOVE_SLIDE_PREVIEW,
EXTERNAL
}

View file

@ -62,6 +62,20 @@ public class Stopwatch {
timerEnded = false;
}
/**
* Starts the current stopwatch.
*/
public void startStopwatch() {
runStopwatch = true;
}
/**
* Stops the current stopwatch.
*/
public void stopStopwatch() {
runStopwatch = false;
}
/**
* Switching between running and paused stopwatch.
*/
@ -135,13 +149,6 @@ public class Stopwatch {
}
}
/**
* Stops the current stopwatch
*/
public void stopStopwatch() {
runStopwatch = false;
}
public StopwatchType getType() {
return type;
}

View file

@ -27,6 +27,7 @@ import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.BookmarkException;
import org.lecturestudio.presenter.api.model.BookmarkKeyException;
import org.lecturestudio.presenter.api.model.BookmarkExistsException;
import org.lecturestudio.presenter.api.model.Bookmarks;
import org.lecturestudio.presenter.api.service.BookmarkService;
import org.lecturestudio.presenter.api.view.CreateBookmarkView;
@ -58,10 +59,13 @@ public class CreateBookmarkPresenter extends Presenter<CreateBookmarkView> {
bookmarkCreated(bookmarkService.createBookmark(keyStr));
}
catch (BookmarkKeyException e) {
context.showError("bookmark.assign.error", "bookmark.key.exists", keyStr);
context.showError("bookmark.assign.warning", "bookmark.key.exists", keyStr);
}
catch (BookmarkExistsException e){
context.showError("bookmark.assign.warning", "bookmark.exists");
}
catch (BookmarkException e) {
handleException(e, "Create bookmark failed", "bookmark.assign.error");
handleException(e, "Create bookmark failed", "bookmark.assign.warning");
}
}

View file

@ -21,22 +21,29 @@ package org.lecturestudio.presenter.api.presenter;
import javax.inject.Inject;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.core.service.DocumentService;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.BookmarkKeyException;
import org.lecturestudio.presenter.api.model.Bookmarks;
import org.lecturestudio.presenter.api.service.BookmarkService;
import org.lecturestudio.presenter.api.view.GotoBookmarkView;
public class GotoBookmarkPresenter extends Presenter<GotoBookmarkView> {
private final DocumentService documentService;
private final BookmarkService bookmarkService;
private Document selectedDocument;
@Inject
GotoBookmarkPresenter(ApplicationContext context, GotoBookmarkView view, BookmarkService bookmarkService) {
GotoBookmarkPresenter(ApplicationContext context, GotoBookmarkView view, DocumentService documentService,
BookmarkService bookmarkService) {
super(context, view);
this.documentService = documentService;
this.bookmarkService = bookmarkService;
}
@ -45,9 +52,15 @@ public class GotoBookmarkPresenter extends Presenter<GotoBookmarkView> {
Bookmarks bookmarks = bookmarkService.getBookmarks();
view.setOnClose(this::close);
view.setOnGotoPageNumber(this::gotoPageNumber);
view.setOnGotoBookmark(this::gotoBookmark);
view.setOnDeleteBookmark(this::deleteBookmark);
view.setBookmarks(bookmarks.getAllBookmarks());
view.setDocument(selectedDocument);
}
public void setSelectedDocument(Document document) {
selectedDocument = document;
}
private void deleteBookmark(Bookmark bookmark) {
@ -61,15 +74,24 @@ public class GotoBookmarkPresenter extends Presenter<GotoBookmarkView> {
}
private void gotoBookmark(Bookmark bookmark) {
try {
bookmarkService.gotoBookmark(bookmark);
close();
}
catch (BookmarkKeyException e) {
if (!bookmarkService.hasBookmark(bookmark)) {
context.showError("bookmark.goto.error", "bookmark.key.not.existing", bookmark.getShortcut());
return;
}
try {
close();
bookmarkService.gotoBookmark(bookmark);
}
catch (Exception e) {
handleException(e, "Go to bookmark failed", "bookmark.goto.error");
}
}
private void gotoPageNumber(Integer pageNumber) {
close();
documentService.selectPage(pageNumber);
}
}

View file

@ -54,6 +54,7 @@ import org.lecturestudio.core.presenter.command.CloseApplicationCommand;
import org.lecturestudio.core.presenter.command.ClosePresenterCommand;
import org.lecturestudio.core.presenter.command.ShowPresenterCommand;
import org.lecturestudio.core.service.DocumentService;
import org.lecturestudio.core.util.FileUtils;
import org.lecturestudio.core.util.ObservableHashMap;
import org.lecturestudio.core.util.ObservableMap;
import org.lecturestudio.core.util.ShutdownHandler;
@ -168,7 +169,18 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
@Override
public void openFile(File file) {
// No file associations yet.
if (isNull(file)) {
return;
}
showWaitingNotification("open.document");
documentService.openDocument(file).thenRun( () -> {
hideWaitingNotification();
}).exceptionally(throwable -> {
hideWaitingNotification();
handleException(throwable, "Open document failed",
"open.document.error", file.getPath());
return null;
});
}
@Override

View file

@ -28,12 +28,10 @@ import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -46,14 +44,11 @@ import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.audio.AudioDeviceNotConnectedException;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.bus.EventBus;
import org.lecturestudio.core.bus.event.CustomizeToolbarEvent;
import org.lecturestudio.core.bus.event.DocumentEvent;
import org.lecturestudio.core.bus.event.PageEvent;
import org.lecturestudio.core.bus.event.ViewVisibleEvent;
import org.lecturestudio.core.bus.event.*;
import org.lecturestudio.core.controller.ToolController;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.model.RecentDocument;
import org.lecturestudio.core.model.listener.PageEditEvent;
@ -67,23 +62,20 @@ import org.lecturestudio.core.service.DocumentService;
import org.lecturestudio.core.util.FileUtils;
import org.lecturestudio.core.util.ListChangeListener;
import org.lecturestudio.core.util.ObservableList;
import org.lecturestudio.core.view.FileChooserView;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.core.view.PresentationParameterProvider;
import org.lecturestudio.core.view.View;
import org.lecturestudio.core.view.ViewContextFactory;
import org.lecturestudio.core.view.ViewType;
import org.lecturestudio.core.view.*;
import org.lecturestudio.presenter.api.config.PresenterConfiguration;
import org.lecturestudio.presenter.api.config.SlideViewConfiguration;
import org.lecturestudio.presenter.api.context.PresenterContext;
import org.lecturestudio.presenter.api.event.ExternalMessagesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalNotesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSlideNotesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalParticipantsViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSlidePreviewViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSpeechViewEvent;
import org.lecturestudio.presenter.api.event.MessageBarPositionEvent;
import org.lecturestudio.presenter.api.event.MessengerStateEvent;
import org.lecturestudio.presenter.api.event.NotesBarPositionEvent;
import org.lecturestudio.presenter.api.event.SlideNotesBarPositionEvent;
import org.lecturestudio.presenter.api.event.ParticipantsPositionEvent;
import org.lecturestudio.presenter.api.event.PreviewPositionEvent;
import org.lecturestudio.presenter.api.event.QuizStateEvent;
@ -91,13 +83,8 @@ import org.lecturestudio.presenter.api.event.RecordingStateEvent;
import org.lecturestudio.presenter.api.event.RecordingTimeEvent;
import org.lecturestudio.presenter.api.event.StreamReconnectStateEvent;
import org.lecturestudio.presenter.api.event.StreamingStateEvent;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.BookmarkKeyException;
import org.lecturestudio.presenter.api.model.Bookmarks;
import org.lecturestudio.presenter.api.model.BookmarksListener;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.model.Stopwatch;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.presenter.command.GotoBookmarkCommand;
import org.lecturestudio.presenter.api.presenter.command.StopwatchCommand;
import org.lecturestudio.presenter.api.service.BookmarkService;
import org.lecturestudio.presenter.api.service.QuizWebServiceState;
@ -111,6 +98,8 @@ public class MenuPresenter extends Presenter<MenuView> {
/** Mainly used for Desktop.getDesktop().open to circumvent errors. */
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final Map<Class<?>, Object> viewPositionMap = new HashMap<>();
private final DateTimeFormatter timeFormatter;
private final Timer timer;
@ -159,6 +148,10 @@ public class MenuPresenter extends Presenter<MenuView> {
view.setDocument(doc);
if (nonNull(doc)) {
view.setSplitNotesPosition(doc.getSplitSlideNotesPosition());
}
pageChanged(page);
}
@ -230,27 +223,62 @@ public class MenuPresenter extends Presenter<MenuView> {
@Subscribe
public void onEvent(final ExternalMessagesViewEvent event) {
view.setExternalMessages(event.isEnabled(), event.isShow());
if (!event.isEnabled()) {
// Set the previous position.
MessageBarPosition position = getViewPosition(MessageBarPosition.class);
if (nonNull(position)) {
view.setMessagesPosition(position);
}
}
}
@Subscribe
public void onEvent(final ExternalParticipantsViewEvent event) {
view.setExternalParticipants(event.isEnabled(), event.isShow());
if (!event.isEnabled()) {
// Set the previous position.
ParticipantsPosition position = getViewPosition(ParticipantsPosition.class);
if (nonNull(position)) {
view.setParticipantsPosition(position);
}
}
}
@Subscribe
public void onEvent(final ExternalSlidePreviewViewEvent event) {
view.setExternalSlidePreview(event.isEnabled(), event.isShow());
}
if (!event.isEnabled()) {
// Set the previous position.
SlidePreviewPosition position = getViewPosition(SlidePreviewPosition.class);
@Subscribe
public void onEvent(final ExternalSpeechViewEvent event) {
view.setExternalSpeech(event.isEnabled(), event.isShow());
if (nonNull(position)) {
view.setSlidePreviewPosition(position);
}
}
}
@Subscribe
public void onEvent(final ExternalNotesViewEvent event) {
view.setExternalNotes(event.isEnabled(), event.isShow());
if (!event.isEnabled()) {
// Set the previous position.
SlideNotesPosition position = getViewPosition(SlideNotesPosition.class);
if (nonNull(position)) {
view.setSlideNotesPosition(position);
}
}
}
@Subscribe
public void onEvent(final ExternalSlideNotesViewEvent event) {
if (!event.isEnabled()) {
// Set the previous position.
NoteSlidePosition position = getViewPosition(NoteSlidePosition.class);
if (nonNull(position)) {
view.setNoteSlidePosition(position);
}
}
}
public void openBookmark(Bookmark bookmark) {
@ -265,6 +293,20 @@ public class MenuPresenter extends Presenter<MenuView> {
}
}
public void openPrevBookmark(){
Page page = bookmarkService.getPrevBookmarkPage();
if (nonNull(page)) {
documentService.selectPage(page);
}
}
public void openNextBookmark(){
Page page = bookmarkService.getNextBookmarkPage();
if (nonNull(page)) {
documentService.selectPage(page);
}
}
public void openDocument(File documentFile) {
documentService.openDocument(documentFile)
.exceptionally(throwable -> {
@ -301,36 +343,84 @@ public class MenuPresenter extends Presenter<MenuView> {
eventBus.post(new CustomizeToolbarEvent());
}
public void externalMessages(boolean selected) {
eventBus.post(new ExternalMessagesViewEvent(selected));
}
public void positionSpeech(SpeechPosition position) {
if (position == SpeechPosition.EXTERNAL) {
eventBus.post(new ExternalSpeechViewEvent(true));
public void externalParticipants(boolean selected) {
eventBus.post(new ExternalParticipantsViewEvent(selected));
}
public void externalSlidePreview(boolean selected) {
eventBus.post(new ExternalSlidePreviewViewEvent(selected));
}
public void externalSpeech(boolean selected) {
eventBus.post(new ExternalSpeechViewEvent(selected));
}
public void externalNotes(boolean selected) {
eventBus.post(new ExternalNotesViewEvent(selected));
// getPresenterConfig().getSlideViewConfiguration().setSpeechPosition(position);
}
else {
setViewPosition(SpeechPosition.class, position);
}
}
public void positionMessages(MessageBarPosition position) {
eventBus.post(new MessageBarPositionEvent(position));
if (position == MessageBarPosition.EXTERNAL) {
eventBus.post(new ExternalMessagesViewEvent(true));
// getPresenterConfig().getSlideViewConfiguration().setMessageBarPosition(position);
}
else {
setViewPosition(MessageBarPosition.class, position);
eventBus.post(new MessageBarPositionEvent(position));
}
}
public void positionNotes(NoteBarPosition position) {
eventBus.post(new NotesBarPositionEvent(position));
public void positionParticipants(ParticipantsPosition position) {
if (position == ParticipantsPosition.EXTERNAL) {
eventBus.post(new ExternalParticipantsViewEvent(true));
// getPresenterConfig().getSlideViewConfiguration().setParticipantsPosition(position);
}
else {
setViewPosition(ParticipantsPosition.class, position);
eventBus.post(new ParticipantsPositionEvent(position));
}
}
public void positionParticipants(MessageBarPosition position) {
eventBus.post(new ParticipantsPositionEvent(position));
public void positionSlidePreview(SlidePreviewPosition position) {
if (position == SlidePreviewPosition.EXTERNAL) {
eventBus.post(new ExternalSlidePreviewViewEvent(true));
// getPresenterConfig().getSlideViewConfiguration().setSlidePreviewPosition(position);
}
else {
setViewPosition(SlidePreviewPosition.class, position);
eventBus.post(new PreviewPositionEvent(position));
}
}
public void positionSlideNotes(SlideNotesPosition position) {
if (position == SlideNotesPosition.EXTERNAL) {
eventBus.post(new ExternalNotesViewEvent(true));
// getPresenterConfig().getSlideViewConfiguration().setSlideNotesPosition(position);
}
else {
setViewPosition(SlideNotesPosition.class, position);
eventBus.post(new NotesBarPositionEvent(position));
}
}
public void positionNoteSlide(NoteSlidePosition position) {
if (position == NoteSlidePosition.EXTERNAL) {
eventBus.post(new ExternalSlideNotesViewEvent(true));
// getPresenterConfig().getSlideViewConfiguration().setNoteSlidePosition(position);
}
else {
setViewPosition(NoteSlidePosition.class, position);
eventBus.post(new SlideNotesBarPositionEvent(position));
}
}
public void positionSplitNotes(NotesPosition position){
documentService.selectNotesPosition(position);
}
public void newWhiteboard() {
@ -430,8 +520,52 @@ public class MenuPresenter extends Presenter<MenuView> {
eventBus.post(new ShowPresenterCommand<>(CreateBookmarkPresenter.class));
}
public void newDefaultBookmark() {
try {
bookmarkCreated(bookmarkService.createDefaultBookmark());
}
catch (BookmarkExistsException e) {
Page page = documentService.getDocuments().getSelectedDocument().getCurrentPage();
String message = MessageFormat.format(context.getDictionary().get("bookmark.exists"), page.getPageNumber());
context.showNotification(NotificationType.WARNING, "bookmark.assign.warning", message);
}
catch (BookmarkException e) {
handleException(e, "Create bookmark failed", "bookmark.assign.warning");
}
}
public void removeBookmark() {
try {
if (nonNull(bookmarkService.getPageBookmark())) {
String shortcut = bookmarkService.getPageBookmark().getShortcut();
bookmarkService.deleteBookmark(bookmarkService.getPageBookmark());
bookmarkRemoved(shortcut);
}
}
catch (BookmarkException e) {
handleException(e, "Remove bookmark failed", "bookmark.assign.warning");
}
}
private void bookmarkRemoved(String shortcut) {
String message = MessageFormat.format(context.getDictionary().get("bookmark.removed"), shortcut);
context.showNotificationPopup(message);
close();
}
private void bookmarkCreated(Bookmark bookmark) {
String shortcut = bookmark.getShortcut().toUpperCase();
String message = MessageFormat.format(context.getDictionary().get("bookmark.created"), shortcut);
context.showNotificationPopup(message);
close();
}
public void gotoBookmark() {
eventBus.post(new ShowPresenterCommand<>(GotoBookmarkPresenter.class));
Document selectedDoc = documentService.getDocuments().getSelectedDocument();
eventBus.post(new GotoBookmarkCommand(selectedDoc));
}
public void previousBookmark() {
@ -508,6 +642,15 @@ public class MenuPresenter extends Presenter<MenuView> {
return (PresenterConfiguration) context.getConfiguration();
}
@SuppressWarnings("unchecked")
private <T> T getViewPosition(Class<T> cls) {
return (T) viewPositionMap.getOrDefault(cls, null);
}
private <T> void setViewPosition(Class<T> cls, T value) {
viewPositionMap.put(cls, value);
}
@Override
public void initialize() {
final PresenterContext presenterContext = (PresenterContext) context;
@ -516,6 +659,13 @@ public class MenuPresenter extends Presenter<MenuView> {
eventBus.register(this);
setViewPosition(MessageBarPosition.class, slideViewConfig.getMessageBarPosition());
setViewPosition(ParticipantsPosition.class, slideViewConfig.getParticipantsPosition());
setViewPosition(SlidePreviewPosition.class, slideViewConfig.getSlidePreviewPosition());
setViewPosition(SlideNotesPosition.class, slideViewConfig.getSlideNotesPosition());
setViewPosition(NoteSlidePosition.class, slideViewConfig.getNoteSlidePosition());
setViewPosition(SpeechPosition.class, slideViewConfig.getSpeechPosition());
view.setRecordingState(ExecutableState.Stopped);
view.setMessengerState(ExecutableState.Stopped);
view.setStreamingState(ExecutableState.Stopped);
@ -543,44 +693,26 @@ public class MenuPresenter extends Presenter<MenuView> {
view.setOnCustomizeToolbar(this::customizeToolbar);
view.setOnExternalMessages(this::externalMessages);
view.setOnExternalParticipants(this::externalParticipants);
view.setOnExternalSlidePreview(this::externalSlidePreview);
view.setOnExternalSpeech(this::externalSpeech);
view.setOnExternalNotes(this::externalNotes);
view.setSpeechPosition(slideViewConfig.getSpeechPosition());
view.setOnSpeechPosition(this::positionSpeech);
switch (slideViewConfig.getMessageBarPosition()) {
case LEFT -> view.setMessagesPositionLeft();
case BOTTOM -> view.setMessagesPositionBottom();
case RIGHT -> view.setMessagesPositionRight();
}
view.setMessagesPosition(slideViewConfig.getMessageBarPosition());
view.setOnMessagesPosition(this::positionMessages);
view.setOnMessagesPositionLeft(() -> positionMessages(MessageBarPosition.LEFT));
view.setOnMessagesPositionBottom(() -> positionMessages(MessageBarPosition.BOTTOM));
view.setOnMessagesPositionRight(() -> positionMessages(MessageBarPosition.RIGHT));
view.setSlideNotesPosition(slideViewConfig.getSlideNotesPosition());
view.setOnSlideNotesPosition(this::positionSlideNotes);
switch (slideViewConfig.getNotesBarPosition()) {
case LEFT -> view.setNotesPositionLeft();
case BOTTOM -> view.setNotesPositionBottom();
}
view.setNoteSlidePosition(slideViewConfig.getNoteSlidePosition());
view.setOnNoteSlidePosition(this::positionNoteSlide);
view.setOnNotesPositionLeft(() -> positionNotes(NoteBarPosition.LEFT));
view.setOnNotesPositionBottom(() -> positionNotes(NoteBarPosition.BOTTOM));
view.setParticipantsPosition(slideViewConfig.getParticipantsPosition());
view.setOnParticipantsPosition(this::positionParticipants);
switch (slideViewConfig.getParticipantsPosition()) {
case LEFT -> view.setParticipantsPositionLeft();
case RIGHT -> view.setParticipantsPositionRight();
}
view.setSlidePreviewPosition(slideViewConfig.getSlidePreviewPosition());
view.setOnSlidePreviewPosition(this::positionSlidePreview);
view.setOnParticipantsPositionLeft(() -> positionParticipants(MessageBarPosition.LEFT));
view.setOnParticipantsPositionRight(() -> positionParticipants(MessageBarPosition.RIGHT));
ObjectProperty<MessageBarPosition> previewPosition = slideViewConfig.previewPositionProperty();
previewPosition.addListener((o, oldPos, newPos) -> {
eventBus.post(new PreviewPositionEvent(newPos));
});
view.bindPreviewPosition(previewPosition);
view.setSplitNotesPosition(NotesPosition.NONE);
view.setOnSplitNotesPosition(this::positionSplitNotes);
view.setOnNewWhiteboard(this::newWhiteboard);
view.setOnNewWhiteboardPage(this::newWhiteboardPage);
@ -605,8 +737,12 @@ public class MenuPresenter extends Presenter<MenuView> {
view.setOnClearBookmarks(this::clearBookmarks);
view.setOnShowNewBookmarkView(this::newBookmark);
view.setOnRemoveBookmarkView(this::removeBookmark);
view.setOnCreateNewDefaultBookmarkView(this::newDefaultBookmark);
view.setOnShowGotoBookmarkView(this::gotoBookmark);
view.setOnPreviousBookmark(this::previousBookmark);
view.setOnPrevBookmark(this::openPrevBookmark);
view.setOnNextBookmark(this::openNextBookmark);
view.setOnOpenBookmark(this::openBookmark);
view.setOnOpenLog(this::showLog);

View file

@ -27,6 +27,7 @@ import javax.inject.Inject;
import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -90,15 +91,18 @@ import org.lecturestudio.core.view.ViewType;
import org.lecturestudio.presenter.api.config.DocumentTemplateConfiguration;
import org.lecturestudio.presenter.api.config.ExternalWindowConfiguration;
import org.lecturestudio.presenter.api.config.PresenterConfiguration;
import org.lecturestudio.presenter.api.config.SlideViewConfiguration;
import org.lecturestudio.presenter.api.context.PresenterContext;
import org.lecturestudio.presenter.api.event.ExternalMessagesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalNotesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSlideNotesViewEvent;
import org.lecturestudio.presenter.api.event.ExternalParticipantsViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSlidePreviewViewEvent;
import org.lecturestudio.presenter.api.event.ExternalSpeechViewEvent;
import org.lecturestudio.presenter.api.event.MessageBarPositionEvent;
import org.lecturestudio.presenter.api.event.MessengerStateEvent;
import org.lecturestudio.presenter.api.event.NotesBarPositionEvent;
import org.lecturestudio.presenter.api.event.SlideNotesBarPositionEvent;
import org.lecturestudio.presenter.api.event.ParticipantsPositionEvent;
import org.lecturestudio.presenter.api.event.PreviewPositionEvent;
import org.lecturestudio.presenter.api.event.QuizStateEvent;
@ -108,13 +112,8 @@ import org.lecturestudio.presenter.api.event.ScreenShareStateEvent;
import org.lecturestudio.presenter.api.event.StreamReconnectStateEvent;
import org.lecturestudio.presenter.api.event.StreamingStateEvent;
import org.lecturestudio.presenter.api.input.Shortcut;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.MessageDocument;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.service.RecordingService;
import org.lecturestudio.presenter.api.service.WebRtcStreamService;
import org.lecturestudio.presenter.api.service.WebService;
import org.lecturestudio.presenter.api.service.WebServiceInfo;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.service.*;
import org.lecturestudio.presenter.api.view.SlidesView;
import org.lecturestudio.swing.model.ExternalWindowPosition;
import org.lecturestudio.web.api.event.LocalScreenVideoFrameEvent;
@ -161,6 +160,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
private final RenderController renderController;
private final BookmarkService bookmarkService;
private final DocumentService documentService;
private final RecordingService recordingService;
@ -190,6 +191,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
ToolController toolController,
PresentationController presentationController,
RenderController renderController,
BookmarkService bookmarkService,
DocumentService documentService,
DocumentRecorder documentRecorder,
RecordingService recordingService,
@ -203,6 +205,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
this.presentationController = presentationController;
this.renderController = renderController;
this.documentRecorder = documentRecorder;
this.bookmarkService = bookmarkService;
this.documentService = documentService;
this.recordingService = recordingService;
this.webService = webService;
@ -488,9 +491,24 @@ public class SlidesPresenter extends Presenter<SlidesView> {
}
}
@Subscribe
public void onEvent(ExternalSlideNotesViewEvent event) {
if (event.isEnabled()) {
if (event.isShow()) {
viewShowExternalSlideNotes(event.isPersistent());
}
else {
view.hideExternalSlideNotes();
}
}
else {
viewHideExternalSlideNotes(event.isPersistent());
}
}
@Subscribe
public void onEvent(MessageBarPositionEvent event) {
final MessageBarPosition position = event.getMessageBarPosition();
final MessageBarPosition position = event.position();
view.setMessageBarPosition(position);
@ -499,7 +517,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
@Subscribe
public void onEvent(ParticipantsPositionEvent event) {
final MessageBarPosition position = event.getMessageBarPosition();
final ParticipantsPosition position = event.position();
view.setParticipantsPosition(position);
@ -508,18 +526,25 @@ public class SlidesPresenter extends Presenter<SlidesView> {
@Subscribe
public void onEvent(PreviewPositionEvent event) {
final MessageBarPosition position = event.getMessageBarPosition();
view.setPreviewPosition(position);
view.setPreviewPosition(event.position());
}
@Subscribe
public void onEvent(NotesBarPositionEvent event) {
final NoteBarPosition position = event.getNoteBarPosition();
final SlideNotesPosition position = event.position();
view.setNotesBarPosition(position);
view.setNotesPosition(position);
getPresenterConfig().getSlideViewConfiguration().setNotesBarPosition(position);
getPresenterConfig().getSlideViewConfiguration().setSlideNotesPosition(position);
}
@Subscribe
public void onEvent(SlideNotesBarPositionEvent event) {
final NoteSlidePosition position = event.position();
view.setNoteSlidePosition(position);
getPresenterConfig().getSlideViewConfiguration().setNoteSlidePosition(position);
}
@Subscribe
@ -615,6 +640,21 @@ public class SlidesPresenter extends Presenter<SlidesView> {
eventBus.post(new ExternalNotesViewEvent(false));
}
private void externalSlideNotesPositionChanged(ExternalWindowPosition position) {
final ExternalWindowConfiguration config = getExternalSlideNotesConfig();
config.setPosition(position.getPosition());
config.setScreen(position.getScreen());
}
private void externalSlideNotesSizeChanged(Dimension size) {
getExternalSlideNotesConfig().setSize(size);
}
private void externalSlideNotesClosed() {
eventBus.post(new ExternalSlideNotesViewEvent(false));
}
private void keyEvent(KeyEvent event) {
Action action = shortcutMap.get(event);
@ -850,14 +890,158 @@ public class SlidesPresenter extends Presenter<SlidesView> {
idleTimer.runIdleTask();
}
private void firstPage() {
documentService.selectPage(0);
}
private void lastPage() {
Document doc = documentService.getDocuments().getSelectedDocument();
documentService.selectPage(doc.getPageCount() - 1);
}
private void nextPage() {
documentService.selectNextPage();
}
private void tenPagesForward() {
Document doc = documentService.getDocuments().getSelectedDocument();
int pageNumber = Math.min(doc.getCurrentPageNumber() + 10, doc.getPageCount() - 1);
documentService.selectPage(pageNumber);
}
private void previousPage() {
documentService.selectPreviousPage();
}
private void tenPagesBack() {
Document doc = documentService.getDocuments().getSelectedDocument();
int pageNumber = Math.max(doc.getCurrentPageNumber() - 10, 0);
documentService.selectPage(pageNumber);
}
private void overlayStart() {
Document doc = documentService.getDocuments().getSelectedDocument();
Page page = doc.getCurrentPage();
if (page.isOverlay()) {
Page lastOverlay = null;
var listIter = doc.getPages().listIterator(doc.getPageIndex(page));
while (listIter.hasPrevious()) {
Page previous = listIter.previous();
if (!previous.isOverlay() && nonNull(lastOverlay)) {
documentService.selectPage(lastOverlay);
break;
}
lastOverlay = previous;
}
}
}
private void overlayEnd() {
Document doc = documentService.getDocuments().getSelectedDocument();
Page page = doc.getCurrentPage();
if (page.isOverlay()) {
Page lastOverlay = null;
var listIter = doc.getPages().listIterator(doc.getPageIndex(page));
while (listIter.hasNext()) {
Page next = listIter.next();
if (!next.isOverlay() && nonNull(lastOverlay)) {
documentService.selectPage(lastOverlay);
break;
}
lastOverlay = next;
}
}
}
private void overlayPreviousPage() {
Document doc = documentService.getDocuments().getSelectedDocument();
Page page = doc.getCurrentPage();
if (page.isOverlay()) {
var listIter = doc.getPages().listIterator(doc.getPageIndex(page));
while (listIter.hasPrevious()) {
Page previous = listIter.previous();
if (!previous.isOverlay()) {
documentService.selectPage(previous);
break;
}
}
}
}
private void overlayNextPage() {
Document doc = documentService.getDocuments().getSelectedDocument();
Page page = doc.getCurrentPage();
if (page.isOverlay()) {
var listIter = doc.getPages().listIterator(doc.getPageIndex(page));
while (listIter.hasNext()) {
Page next = listIter.next();
if (!next.isOverlay()) {
documentService.selectPage(next);
break;
}
}
}
}
private void bookmarkSlide() {
try {
bookmarkCreated(bookmarkService.createDefaultBookmark());
}
catch (BookmarkException e) {
handleException(e, "Create bookmark failed", "bookmark.assign.warning", "bookmark.exists");
}
}
private void bookmarkGotoLastSlide() {
Document doc = documentService.getDocuments().getSelectedDocument();
Bookmark bookmark = bookmarkService.getBookmarks().getLastBookmark(doc);
if (nonNull(bookmark)) {
try {
bookmarkService.gotoBookmark(bookmark);
}
catch (BookmarkException e) {
handleException(e, "Go to bookmark failed", "bookmark.goto.error");
}
}
}
private void bookmarkCreated(Bookmark bookmark) {
String shortcut = bookmark.getShortcut().toUpperCase();
String message = MessageFormat.format(context.getDictionary().get("bookmark.created"), shortcut);
context.showNotificationPopup(message);
close();
}
private void timerStart() {
PresenterContext pContext = (PresenterContext) context;
pContext.getStopwatch().startStopwatch();
}
private void timerPause() {
PresenterContext pContext = (PresenterContext) context;
pContext.getStopwatch().stopStopwatch();
}
private void timerReset() {
PresenterContext pContext = (PresenterContext) context;
pContext.getStopwatch().resetStopwatch();
}
private void registerShortcut(Shortcut shortcut, Action action) {
shortcutMap.put(shortcut.getKeyEvent(), action);
}
@ -939,6 +1123,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
for(String note : page.getNotes()){
view.setNotesText(note);
}
view.setSlideNotes(page, parameter);
loadPageObjectViews(page);
recordPage(page);
@ -978,7 +1164,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
}
private PageObjectView<? extends Shape> createPageObjectView(Shape shape,
Class<? extends PageObjectView<? extends Shape>> viewClass) {
Class<? extends PageObjectView<? extends Shape>> viewClass) {
PageObjectView<Shape> objectView = (PageObjectView<Shape>) viewFactory.getInstance(viewClass);
objectView.setPageShape(shape);
objectView.setOnClose(() -> {
@ -1032,6 +1218,10 @@ public class SlidesPresenter extends Presenter<SlidesView> {
return getPresenterConfig().getExternalNotesConfig();
}
private ExternalWindowConfiguration getExternalSlideNotesConfig() {
return getPresenterConfig().getExternalSlideNotesConfig();
}
@Override
public void initialize() {
stylusHandler = new StylusHandler(toolController, () -> {
@ -1151,6 +1341,12 @@ public class SlidesPresenter extends Presenter<SlidesView> {
initExternalScreenBehavior(getExternalNotesConfig(),
(enabled, show) -> eventBus.post(new ExternalNotesViewEvent(enabled, show)));
view.setOnExternalSlideNotesPositionChanged(this::externalSlideNotesPositionChanged);
view.setOnExternalSlideNotesSizeChanged(this::externalSlideNotesSizeChanged);
view.setOnExternalSlideNotesClosed(this::externalSlideNotesClosed);
initExternalScreenBehavior(getExternalSlideNotesConfig(),
(enabled, show) -> eventBus.post(new ExternalSlideNotesViewEvent(enabled, show)));
view.setPageRenderer(renderController);
view.setExtendedFullscreen(config.getExtendedFullscreen());
view.setMessengerState(ExecutableState.Stopped);
@ -1179,30 +1375,50 @@ public class SlidesPresenter extends Presenter<SlidesView> {
view.setOnStopPeerConnection(streamService::stopPeerConnection);
// Register shortcuts that are associated with the SlideView.
registerShortcut(Shortcut.SLIDE_FIRST, this::firstPage);
registerShortcut(Shortcut.SLIDE_LAST, this::lastPage);
registerShortcut(Shortcut.SLIDE_NEXT_DOWN, this::nextPage);
registerShortcut(Shortcut.SLIDE_NEXT_PAGE_DOWN, this::nextPage);
registerShortcut(Shortcut.SLIDE_NEXT_RIGHT, this::nextPage);
registerShortcut(Shortcut.SLIDE_NEXT_SPACE, this::nextPage);
registerShortcut(Shortcut.SLIDE_NEXT_10, this::tenPagesForward);
registerShortcut(Shortcut.SLIDE_PREVIOUS_LEFT, this::previousPage);
registerShortcut(Shortcut.SLIDE_PREVIOUS_PAGE_UP, this::previousPage);
registerShortcut(Shortcut.SLIDE_PREVIOUS_UP, this::previousPage);
registerShortcut(Shortcut.SLIDE_PREVIOUS_BACK_SPACE, this::previousPage);
registerShortcut(Shortcut.SLIDE_PREVIOUS_10, this::tenPagesBack);
registerShortcut(Shortcut.SLIDE_OVERLAY_START, this::overlayStart);
registerShortcut(Shortcut.SLIDE_OVERLAY_END, this::overlayEnd);
registerShortcut(Shortcut.SLIDE_OVERLAY_PREVIOUS, this::overlayPreviousPage);
registerShortcut(Shortcut.SLIDE_OVERLAY_NEXT, this::overlayNextPage);
registerShortcut(Shortcut.BOOKMARK_SLIDE, this::bookmarkSlide);
registerShortcut(Shortcut.BOOKMARK_GOTO_LAST, this::bookmarkGotoLastSlide);
registerShortcut(Shortcut.TIMER_START, this::timerStart);
registerShortcut(Shortcut.TIMER_PAUSE, this::timerPause);
registerShortcut(Shortcut.TIMER_RESET, this::timerReset);
registerShortcut(Shortcut.COPY_OVERLAY, this::copyOverlay);
registerShortcut(Shortcut.COPY_OVERLAY_NEXT_PAGE_CTRL, this::copyNextOverlay);
registerShortcut(Shortcut.COPY_OVERLAY_NEXT_PAGE_SHIFT, this::copyNextOverlay);
view.setMessageBarPosition(getPresenterConfig()
.getSlideViewConfiguration().getMessageBarPosition());
view.setNotesBarPosition(getPresenterConfig()
.getSlideViewConfiguration().getNotesBarPosition());
view.setNotesPosition(getPresenterConfig()
.getSlideViewConfiguration().getSlideNotesPosition());
view.setNoteSlidePosition(getPresenterConfig()
.getSlideViewConfiguration().getNoteSlidePosition());
view.setParticipantsPosition(getPresenterConfig()
.getSlideViewConfiguration().getParticipantsPosition());
view.setPreviewPosition(getPresenterConfig()
.getSlideViewConfiguration().getPreviewPosition());
.getSlideViewConfiguration().getSlidePreviewPosition());
try {
recordingService.init();
@ -1240,16 +1456,32 @@ public class SlidesPresenter extends Presenter<SlidesView> {
}
private void showExternalScreens() {
showExternalScreen(getExternalMessagesConfig(),
(enabled, show) -> eventBus.post(new ExternalMessagesViewEvent(enabled, show)));
showExternalScreen(getExternalParticipantsConfig(),
(enabled, show) -> eventBus.post(new ExternalParticipantsViewEvent(enabled, show)));
showExternalScreen(getExternalSlidePreviewConfig(),
(enabled, show) -> eventBus.post(new ExternalSlidePreviewViewEvent(enabled, show)));
showExternalScreen(getExternalSpeechConfig(),
(enabled, show) -> eventBus.post(new ExternalSpeechViewEvent(enabled, show)));
showExternalScreen(getExternalNotesConfig(),
(enabled, show) -> eventBus.post(new ExternalNotesViewEvent(enabled, show)));
SlideViewConfiguration viewConfig = getPresenterConfig().getSlideViewConfiguration();
if (viewConfig.getMessageBarPosition() == MessageBarPosition.EXTERNAL) {
showExternalScreen(getExternalMessagesConfig(),
(enabled, show) -> eventBus.post(new ExternalMessagesViewEvent(enabled, show)));
}
if (viewConfig.getParticipantsPosition() == ParticipantsPosition.EXTERNAL) {
showExternalScreen(getExternalParticipantsConfig(),
(enabled, show) -> eventBus.post(new ExternalParticipantsViewEvent(enabled, show)));
}
if (viewConfig.getSlidePreviewPosition() == SlidePreviewPosition.EXTERNAL) {
showExternalScreen(getExternalSlidePreviewConfig(),
(enabled, show) -> eventBus.post(new ExternalSlidePreviewViewEvent(enabled, show)));
}
if (viewConfig.getSpeechPosition() == SpeechPosition.EXTERNAL) {
showExternalScreen(getExternalSpeechConfig(),
(enabled, show) -> eventBus.post(new ExternalSpeechViewEvent(enabled, show)));
}
if (viewConfig.getSlideNotesPosition() == SlideNotesPosition.EXTERNAL) {
showExternalScreen(getExternalNotesConfig(),
(enabled, show) -> eventBus.post(new ExternalNotesViewEvent(enabled, show)));
}
if (viewConfig.getNoteSlidePosition() == NoteSlidePosition.EXTERNAL) {
showExternalScreen(getExternalSlideNotesConfig(),
(enabled, show) -> eventBus.post(new ExternalSlideNotesViewEvent(enabled, show)));
}
}
private void showExternalScreen(ExternalWindowConfiguration config, BiConsumer<Boolean, Boolean> action) {
@ -1279,9 +1511,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (persistent) {
config.setEnabled(false);
config.setScreen(null);
config.setPosition(null);
config.setSize(null);
}
view.hideExternalMessages();
@ -1302,9 +1531,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (persistent) {
config.setEnabled(false);
config.setScreen(null);
config.setPosition(null);
config.setSize(null);
}
view.hideExternalParticipants();
@ -1325,9 +1551,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (persistent) {
config.setEnabled(false);
config.setScreen(null);
config.setPosition(null);
config.setSize(null);
}
view.hideExternalSlidePreview();
@ -1348,9 +1571,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (persistent) {
config.setEnabled(false);
config.setScreen(null);
config.setPosition(null);
config.setSize(null);
}
view.hideExternalSpeech();
@ -1371,14 +1591,30 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (persistent) {
config.setEnabled(false);
config.setScreen(null);
config.setPosition(null);
config.setSize(null);
}
view.hideExternalNotes();
}
private void viewShowExternalSlideNotes(boolean persistent) {
final ExternalWindowConfiguration config = getExternalSlideNotesConfig();
if (persistent) {
config.setEnabled(true);
}
view.showExternalSlideNotes(config.getScreen(), config.getPosition(), config.getSize());
}
private void viewHideExternalSlideNotes(boolean persistent) {
final ExternalWindowConfiguration config = getExternalSlideNotesConfig();
if (persistent) {
config.setEnabled(false);
}
view.hideExternalSlideNotes();
}
private boolean checkIfScreenInList(List<Screen> screens, Screen screen) {
if (screen == null) {

View file

@ -25,6 +25,7 @@ import com.google.common.eventbus.Subscribe;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@ -37,10 +38,7 @@ import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.audio.AudioDeviceNotConnectedException;
import org.lecturestudio.core.audio.bus.event.TextFontEvent;
import org.lecturestudio.core.bus.EventBus;
import org.lecturestudio.core.bus.event.CustomizeToolbarEvent;
import org.lecturestudio.core.bus.event.DocumentEvent;
import org.lecturestudio.core.bus.event.PageEvent;
import org.lecturestudio.core.bus.event.ToolSelectionEvent;
import org.lecturestudio.core.bus.event.*;
import org.lecturestudio.core.controller.PresentationController;
import org.lecturestudio.core.controller.ToolController;
import org.lecturestudio.core.graphics.Color;
@ -60,6 +58,7 @@ import org.lecturestudio.core.text.TeXFont;
import org.lecturestudio.core.tool.ColorPalette;
import org.lecturestudio.core.tool.PaintSettings;
import org.lecturestudio.core.tool.ToolType;
import org.lecturestudio.core.view.NotificationType;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.core.view.PresentationParameterProvider;
import org.lecturestudio.core.view.ViewType;
@ -68,8 +67,10 @@ import org.lecturestudio.presenter.api.context.PresenterContext;
import org.lecturestudio.presenter.api.event.RecordingStateEvent;
import org.lecturestudio.presenter.api.event.ScreenShareSelectEvent;
import org.lecturestudio.presenter.api.event.StreamingStateEvent;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.presenter.command.CloseablePresenterCommand;
import org.lecturestudio.presenter.api.presenter.command.StartRecordingCommand;
import org.lecturestudio.presenter.api.service.BookmarkService;
import org.lecturestudio.presenter.api.service.RecordingService;
import org.lecturestudio.presenter.api.view.ToolbarView;
@ -86,6 +87,9 @@ public class ToolbarPresenter extends Presenter<ToolbarView> {
@Inject
private ToolController toolController;
@Inject
private BookmarkService bookmarkService;
@Inject
private PresentationController presentationController;
@ -138,7 +142,28 @@ public class ToolbarPresenter extends Presenter<ToolbarView> {
page.addPageEditedListener(pageEditedListener);
boolean hasBookmark = false;
for(Bookmark bookmark : bookmarkService.getBookmarks().getAllBookmarks()){
if(bookmark.getPage().equals(page)){
hasBookmark = true;
break;
}
}
view.selectNewBookmarkButton(hasBookmark);
pageChanged(page);
}
}
@Subscribe
public void onEvent(final BookmarkEvent event){
if(event.getPage().equals(documentService.getDocuments().getSelectedDocument().getCurrentPage())){
switch (event.getType()){
case CREATED -> view.selectNewBookmarkButton(true);
case REMOVED -> view.selectNewBookmarkButton(false);
default -> view.selectNewBookmarkButton(false);
}
}
}
@ -476,6 +501,69 @@ public class ToolbarPresenter extends Presenter<ToolbarView> {
}
}
/**
* Select the previous bookmark in the bookmark list.
*/
public void selectPreviousBookmark() {
Page page = bookmarkService.getPrevBookmarkPage();
if (nonNull(page)) {
documentService.selectPage(page);
}
}
/**
* Select the next bookmark in the bookmark list.
*/
public void selectNextBookmark() {
Page page = bookmarkService.getNextBookmarkPage();
if (nonNull(page)) {
documentService.selectPage(page);
}
}
/**
* Create a new default bookmark.
*/
private void createNewBookmark() {
try {
Bookmark currBookmark = bookmarkService.getPageBookmark();
if (nonNull(currBookmark)) {
String shortcut = currBookmark.getShortcut();
bookmarkService.deleteBookmark(currBookmark);
bookmarkRemoved(shortcut);
view.selectNewBookmarkButton(false);
}
else {
bookmarkCreated(bookmarkService.createDefaultBookmark());
view.selectNewBookmarkButton(true);
}
}
catch (BookmarkExistsException e) {
Page page = documentService.getDocuments().getSelectedDocument().getCurrentPage();
String message = MessageFormat.format(context.getDictionary().get("bookmark.exists"), page.getPageNumber());
context.showNotification(NotificationType.WARNING, "bookmark.assign.warning", message);
}
catch (BookmarkException e) {
handleException(e, "Create bookmark failed", "bookmark.assign.warning");
}
}
private void bookmarkCreated(Bookmark bookmark) {
String shortcut = bookmark.getShortcut().toUpperCase();
String message = MessageFormat.format(context.getDictionary().get("bookmark.created"), shortcut);
context.showNotificationPopup(message);
close();
}
private void bookmarkRemoved(String shortcut) {
String message = MessageFormat.format(context.getDictionary().get("bookmark.removed"), shortcut);
context.showNotificationPopup(message);
close();
}
@Override
public void initialize() {
PresenterContext presenterContext = (PresenterContext) context;
@ -494,6 +582,11 @@ public class ToolbarPresenter extends Presenter<ToolbarView> {
view.setOnPreviousSlide(toolController::selectPreviousPage);
view.setOnNextSlide(toolController::selectNextPage);
view.setOnPreviousBookmark(this::selectPreviousBookmark);
view.setOnNextBookmark(this::selectNextBookmark);
view.setOnNewBookmark(this::createNewBookmark);
view.setOnCustomPaletteColor(this::customPaletteColor);
view.setOnCustomColor(this::customColor);
view.setOnColor1(this::color1);

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.presenter.api.presenter.command;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.presenter.command.ShowPresenterCommand;
import org.lecturestudio.presenter.api.presenter.GotoBookmarkPresenter;
public class GotoBookmarkCommand extends ShowPresenterCommand<GotoBookmarkPresenter> {
private final Document selectedDocument;
/**
* Create a new {@link GotoBookmarkCommand} with the corresponding presenter class.
*
* @param document The currently selected document.
*/
public GotoBookmarkCommand(Document document) {
super(GotoBookmarkPresenter.class);
selectedDocument = document;
}
@Override
public void execute(GotoBookmarkPresenter presenter) {
presenter.setSelectedDocument(selectedDocument);
}
}

View file

@ -21,11 +21,14 @@ package org.lecturestudio.presenter.api.service;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.bus.event.BookmarkEvent;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.DocumentList;
import org.lecturestudio.core.model.Page;
@ -33,6 +36,7 @@ import org.lecturestudio.core.service.DocumentService;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.BookmarkException;
import org.lecturestudio.presenter.api.model.BookmarkKeyException;
import org.lecturestudio.presenter.api.model.BookmarkExistsException;
import org.lecturestudio.presenter.api.model.Bookmarks;
@Singleton
@ -44,10 +48,15 @@ public class BookmarkService {
private Page prevBookmarkPage;
private int defaultBookmarkCounter = 1;
private final ApplicationContext context;
@Inject
public BookmarkService(DocumentService documentService) {
public BookmarkService(DocumentService documentService, ApplicationContext context) {
this.documentService = documentService;
this.context = context;
this.bookmarks = new Bookmarks();
}
@ -57,11 +66,12 @@ public class BookmarkService {
public void clearBookmarks() {
Document selectedDoc = documentService.getDocuments().getSelectedDocument();
context.getEventBus().post(new BookmarkEvent(documentService.getDocuments().getSelectedDocument().getCurrentPage(), BookmarkEvent.Type.REMOVED));
bookmarks.clear(selectedDoc);
}
public void clearBookmarks(Document document) {
context.getEventBus().post(new BookmarkEvent(documentService.getDocuments().getSelectedDocument().getCurrentPage(), BookmarkEvent.Type.REMOVED));
bookmarks.clear(document);
}
@ -81,20 +91,52 @@ public class BookmarkService {
if (bookmark.getShortcut().equalsIgnoreCase(keyStr)) {
throw new BookmarkKeyException("Bookmark key is already assigned to another bookmark");
}
if (bookmark.getPage().equals(page)){
throw new BookmarkExistsException("Page is already bookmarked");
}
}
}
Bookmark bookmark = new Bookmark(keyStr, page);
bookmarks.add(bookmark);
context.getEventBus().post(new BookmarkEvent(page, BookmarkEvent.Type.CREATED));
return bookmark;
}
public void deleteBookmark(Bookmark bookmark) throws BookmarkException {
context.getEventBus().post(new BookmarkEvent(bookmark.getPage(), BookmarkEvent.Type.REMOVED));
bookmarks.removeBookmark(bookmark);
}
public Bookmark getPageBookmark() throws BookmarkException {
Document selectedDoc = documentService.getDocuments().getSelectedDocument();
Page page = selectedDoc.getCurrentPage();
if (isNull(page)) {
throw new BookmarkException("No document selected");
}
List<Bookmark> docBookmarks = bookmarks.getDocumentBookmarks(selectedDoc);
if (nonNull(docBookmarks)) {
for (Bookmark bookmark : docBookmarks) {
if(bookmark.getPage().equals(page)){
return bookmark;
}
}
}
return null;
}
public boolean hasBookmark(Bookmark bookmark) {
if (isNull(bookmark)) {
throw new NullPointerException("No bookmark provided");
}
Bookmark selectedBookmark = bookmarks.getBookmark(bookmark.getShortcut());
return nonNull(selectedBookmark);
}
public void gotoBookmark(Bookmark bookmark) throws BookmarkException {
if (isNull(bookmark)) {
throw new NullPointerException("No bookmark provided");
@ -138,4 +180,98 @@ public class BookmarkService {
documentService.selectPage(page);
}
/**
* Creates a bookmark with a default shortcut
*
* @return the new created bookmark
*/
public Bookmark createDefaultBookmark() throws BookmarkException{
Bookmark bookmark = createBookmark("L" + defaultBookmarkCounter);
defaultBookmarkCounter++;
return bookmark;
}
/**
* Calculates the next bookmark where the pagenumber is lower than current pagenumber.
*
* @return Page with bookmark with the next lower pagenumber
*/
public Page getPrevBookmarkPage(){
Document currDoc = documentService.getDocuments().getSelectedDocument();
List<Bookmark> allDocBookmarks = bookmarks.getDocumentBookmarks(currDoc);
if(!nonNull(allDocBookmarks)){
return null;
}
Bookmark currBookmark = getBookmarkCurrentPage(allDocBookmarks);
if(!nonNull(currBookmark)){
createDefaultBookmarkCurrentPage();
currBookmark = getBookmarks().getBookmark("L0");
}
int maxBookmarks = allDocBookmarks.size();
allDocBookmarks.sort(Comparator.comparingInt(a -> a.getPage().getPageNumber()));
int bookmarkPos = allDocBookmarks.indexOf(currBookmark);
if(maxBookmarks == 0 || bookmarkPos == 0){
return null;
}
bookmarkPos--;
Bookmark bookmark = allDocBookmarks.get(bookmarkPos);
return bookmark.getPage();
}
/**
* Calculates the next bookmark where the pagenumber is higher than current pagenumber.
*
* @return Page with bookmark with the next higher pagenumber
*/
public Page getNextBookmarkPage(){
Document currDoc = documentService.getDocuments().getSelectedDocument();
List<Bookmark> allDocBookmarks = bookmarks.getDocumentBookmarks(currDoc);
if(!nonNull(allDocBookmarks)){
return null;
}
Bookmark currBookmark = getBookmarkCurrentPage(allDocBookmarks);
if(!nonNull(currBookmark)){
createDefaultBookmarkCurrentPage();
currBookmark = getBookmarks().getBookmark("L0");
}
int maxBookmarks = allDocBookmarks.size();
allDocBookmarks.sort(Comparator.comparingInt(a -> a.getPage().getPageNumber()));
int bookmarkPos = allDocBookmarks.indexOf(currBookmark);
if(bookmarkPos + 1 == maxBookmarks){
return null;
}
bookmarkPos++;
Bookmark bookmark = allDocBookmarks.get(bookmarkPos);
return bookmark.getPage();
}
private Bookmark getBookmarkCurrentPage(List<Bookmark> allDocBookmarks){
Page currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage();
for(Bookmark bm : allDocBookmarks){
if(bm.getPage().equals(currPage)){
return bm;
}
}
return null;
}
private Bookmark createDefaultBookmarkCurrentPage(){
try {
if (nonNull(getBookmarks().getBookmark("L0"))) {
deleteBookmark(getBookmarks().getBookmark("L0"));
}
return createBookmark("L0");
} catch (BookmarkException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -20,6 +20,7 @@ package org.lecturestudio.presenter.api.view;
import java.util.List;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.core.view.View;
@ -27,12 +28,16 @@ import org.lecturestudio.presenter.api.model.Bookmark;
public interface GotoBookmarkView extends View {
void setDocument(Document document);
void setBookmarks(List<Bookmark> bookmarkList);
void removeBookmark(Bookmark bookmark);
void setOnClose(Action action);
void setOnGotoPageNumber(ConsumerAction<Integer> action);
void setOnDeleteBookmark(ConsumerAction<Bookmark> action);
void setOnGotoBookmark(ConsumerAction<Bookmark> action);

View file

@ -26,18 +26,13 @@ import org.lecturestudio.core.ExecutableState;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.IntegerProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.model.RecentDocument;
import org.lecturestudio.core.model.Time;
import org.lecturestudio.core.model.*;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.core.view.View;
import org.lecturestudio.presenter.api.context.PresenterContext.ParticipantCount;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.Bookmarks;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.service.QuizWebServiceState;
public interface MenuView extends View {
@ -78,64 +73,33 @@ public interface MenuView extends View {
void bindShowOutline(BooleanProperty showProperty);
void setAdvancedSettings(boolean selected);
void bindFullscreen(BooleanProperty fullscreen);
void setOnAdvancedSettings(ConsumerAction<Boolean> action);
void setOnCustomizeToolbar(Action action);
void setExternalMessages(boolean selected, boolean show);
void setSpeechPosition(SpeechPosition position);
void setOnExternalMessages(ConsumerAction<Boolean> action);
void setOnSpeechPosition(ConsumerAction<SpeechPosition> action);
void setExternalParticipants(boolean selected, boolean show);
void setMessagesPosition(MessageBarPosition position);
void setOnExternalParticipants(ConsumerAction<Boolean> action);
void setOnMessagesPosition(ConsumerAction<MessageBarPosition> action);
void setExternalSlidePreview(boolean selected, boolean show);
void setSlideNotesPosition(SlideNotesPosition position);
void setOnExternalSlidePreview(ConsumerAction<Boolean> action);
void setOnSlideNotesPosition(ConsumerAction<SlideNotesPosition> action);
void setExternalSpeech(boolean selected, boolean show);
void setNoteSlidePosition(NoteSlidePosition position);
void setOnExternalSpeech(ConsumerAction<Boolean> action);
void setOnNoteSlidePosition(ConsumerAction<NoteSlidePosition> action);
void setExternalNotes(boolean selected, boolean show);
void setParticipantsPosition(ParticipantsPosition position);
void setOnExternalNotes(ConsumerAction<Boolean> action);
void setOnParticipantsPosition(ConsumerAction<ParticipantsPosition> action);
void setOnMessagesPositionLeft(Action action);
void setSlidePreviewPosition(SlidePreviewPosition position);
void setMessagesPositionLeft();
void setOnMessagesPositionBottom(Action action);
void setMessagesPositionBottom();
void setOnMessagesPositionRight(Action action);
void setMessagesPositionRight();
void setOnNotesPositionLeft(Action action);
void setNotesPositionLeft();
void setOnNotesPositionBottom(Action action);
void setNotesPositionBottom();
void setOnParticipantsPositionLeft(Action action);
void setParticipantsPositionLeft();
void setOnParticipantsPositionRight(Action action);
void setParticipantsPositionRight();
void bindPreviewPosition(ObjectProperty<MessageBarPosition> position);
void setOnSlidePreviewPosition(ConsumerAction<SlidePreviewPosition> action);
/**
* Whiteboard Menu
@ -209,10 +173,18 @@ public interface MenuView extends View {
void setOnShowNewBookmarkView(Action action);
void setOnCreateNewDefaultBookmarkView(Action action);
void setOnRemoveBookmarkView(Action action);
void setOnShowGotoBookmarkView(Action action);
void setOnPreviousBookmark(Action action);
void setOnPrevBookmark(Action action);
void setOnNextBookmark(Action action);
void setOnOpenBookmark(ConsumerAction<Bookmark> action);
/**
@ -250,4 +222,13 @@ public interface MenuView extends View {
void bindCourseParticipantsCount(ObjectProperty<ParticipantCount> count);
void setQuizServiceState(QuizWebServiceState state);
/**
* Split notes
*/
void setSplitNotesPosition(NotesPosition position);
void setOnSplitNotesPosition(ConsumerAction<NotesPosition> action);
}

View file

@ -34,8 +34,7 @@ import org.lecturestudio.core.model.DocumentOutline;
import org.lecturestudio.core.model.DocumentOutlineItem;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.view.*;
import org.lecturestudio.presenter.api.model.NoteBarPosition;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.config.SlideViewConfiguration;
import org.lecturestudio.swing.model.ExternalWindowPosition;
import org.lecturestudio.core.stylus.StylusHandler;
@ -73,6 +72,8 @@ public interface SlidesView extends View {
void setPage(Page page, PresentationParameter parameter);
void setSlideNotes(Page page, PresentationParameter parameter);
void setPageRenderer(RenderController pageRenderer);
void setOutline(DocumentOutline outline);
@ -175,6 +176,12 @@ public interface SlidesView extends View {
void setOnExternalNotesClosed(Action action);
void setOnExternalSlideNotesPositionChanged(ConsumerAction<ExternalWindowPosition> action);
void setOnExternalSlideNotesSizeChanged(ConsumerAction<Dimension> action);
void setOnExternalSlideNotesClosed(Action action);
void showExternalMessages(Screen screen, Point position, Dimension size);
void hideExternalMessages();
@ -195,12 +202,18 @@ public interface SlidesView extends View {
void hideExternalNotes();
void showExternalSlideNotes(Screen screen, Point position, Dimension size);
void hideExternalSlideNotes();
void setMessageBarPosition(MessageBarPosition position);
void setNotesBarPosition(NoteBarPosition position);
void setNotesPosition(SlideNotesPosition position);
void setParticipantsPosition(MessageBarPosition position);
void setNoteSlidePosition(NoteSlidePosition position);
void setPreviewPosition(MessageBarPosition position);
void setParticipantsPosition(ParticipantsPosition position);
void setPreviewPosition(SlidePreviewPosition position);
}

View file

@ -56,6 +56,12 @@ public interface ToolbarView extends View {
void setOnNextSlide(Action action);
void setOnPreviousBookmark(Action action);
void setOnNextBookmark(Action action);
void setOnNewBookmark(Action action);
void setOnCustomPaletteColor(ConsumerAction<Color> action);
void setOnCustomColor(Action action);
@ -122,6 +128,8 @@ public interface ToolbarView extends View {
void selectColorButton(ToolType toolType, PaintSettings settings);
void selectNewBookmarkButton(boolean hasBookmark);
void selectToolButton(ToolType toolType);
void openCustomizeToolbarDialog();

View file

@ -1,12 +1,14 @@
audio.device.connected = Audiogerät verbunden
audio.device.disconnected = Audiogerät getrennt
audio.device.connected = Audiogerät verbunden
audio.device.disconnected = Audiogerät getrennt
bookmark.assign.error = Lesezeichen konnte nicht erstellt werden
bookmark.assign.warning = Lesezeichen konnte nicht erstellt werden
bookmark.delete.error = Lesezeichen konnte nicht gel\u00f6scht werden
bookmark.goto.error = Lesezeichen konnte nicht angesprungen werden
bookmark.key.exists = Ein Lesezeichen mit der Taste {0} existiert bereits
bookmark.key.not.existing = Ein Lesezeichen mit der Taste {0} existiert nicht
bookmark.key.exists = Ein Lesezeichen mit der Taste {0} existiert bereits.
bookmark.exists = Ein Lesezeichen existiert bereits f\u00fcr die aktuelle Folie.
bookmark.key.not.existing = Ein Lesezeichen mit der Taste {0} existiert nicht.
bookmark.created = Neues Lesezeichen erstellt: {0}
bookmark.removed = Lesezeichen gel\u00f6scht: {0}
document.save.error = Dokument konnte nicht gespeichert werden
document.save.lecture = Vorlesung
@ -65,6 +67,7 @@ message.slide.create.error = Nachrichtenfolie konnte nicht erstellt werden
service.timeout.error = Der Dienst ist derzeit nicht verf\u00fcgbar. Sollte der Fehler dauerhaft auftreten, so wenden Sie sich bitte an den Administrator ihres Vertrauens.
open.document = Dokument wird geladen
open.document.error = Dokument konnte nicht ge\u00f6ffnet werden
open.page.file.error = Die Datei konnte nicht ge\u00f6ffnet werden
open.page.uri.error = Der Link konnte nicht ge\u00f6ffnet werden

View file

@ -1,12 +1,14 @@
audio.device.connected = Audio device connected
audio.device.disconnected = Audio device disconnected
bookmark.assign.error = Bookmark could not be created
bookmark.assign.warning = Bookmark could not be created
bookmark.delete.error = Bookmark could not be deleted
bookmark.goto.error = Bookmark could not be opened
bookmark.key.exists = A bookmark with the key {0} already exists
bookmark.key.not.existing = A bookmark with the key {0} does not exist
bookmark.key.exists = A bookmark with the key {0} already exists.
bookmark.exists = A bookmark already exists for the current slide.
bookmark.key.not.existing = A bookmark with the key {0} does not exist.
bookmark.created = New bookmark created: {0}
bookmark.removed = Bookmark removed: {0}
document.save.error = Document could not be saved
document.save.lecture = Lecture
@ -65,6 +67,7 @@ message.slide.create.error = Message slide could not be created
service.timeout.error = The service is currently not available. If the error occurs permanently, please contact your trusted administrator.
open.document = loading document
open.document.error = Document could not be opened
open.page.file.error = The file could not be opened
open.page.uri.error = The link could not be opened

View file

@ -44,17 +44,19 @@ class CreateBookmarkPresenterTest extends PresenterTest {
private BookmarkService bookmarkService;
private DocumentService documentService;
@BeforeEach
void setup() throws IOException {
Document document = new Document();
document.createPage();
document.createPage();
DocumentService documentService = new DocumentService(context);
documentService = new DocumentService(context);
documentService.addDocument(document);
documentService.selectDocument(document);
bookmarkService = new BookmarkService(documentService);
bookmarkService = new BookmarkService(documentService, context);
}
@Test
@ -74,6 +76,9 @@ class CreateBookmarkPresenterTest extends PresenterTest {
@Test
void testBookmarkList() throws BookmarkException {
Bookmark a = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark z = bookmarkService.createBookmark("z");
CreateBookmarkMockView view = new CreateBookmarkMockView() {
@ -91,6 +96,29 @@ class CreateBookmarkPresenterTest extends PresenterTest {
presenter.initialize();
}
@Test
void testDefaultBookmarkList() throws BookmarkException {
Bookmark l1 = bookmarkService.createDefaultBookmark();
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark l2 = bookmarkService.createDefaultBookmark();
CreateBookmarkMockView view = new CreateBookmarkMockView() {
@Override
public void setBookmarks(List<Bookmark> bookmarkList) {
assertNotNull(bookmarkList);
assertFalse(bookmarkList.isEmpty());
assertEquals(2, bookmarkList.size());
assertEquals(l1, bookmarkList.get(0));
assertEquals(l2, bookmarkList.get(1));
}
};
CreateBookmarkPresenter presenter = new CreateBookmarkPresenter(context, view, bookmarkService);
presenter.initialize();
}
@Test
void testCreateBookmark() {
AtomicBoolean close = new AtomicBoolean(false);
@ -130,13 +158,41 @@ class CreateBookmarkPresenterTest extends PresenterTest {
assertFalse(close.get());
assertNotNull(notifyView);
assertEquals(NotificationType.ERROR, notifyView.type);
assertEquals("bookmark.assign.error", notifyView.title);
assertEquals("bookmark.assign.warning", notifyView.title);
assertEquals("bookmark.key.exists", notifyView.message);
}
@Test
void testCreateInvalidBookmarkSamePage() throws BookmarkException {
bookmarkService.createBookmark("a");
AtomicBoolean close = new AtomicBoolean(false);
CreateBookmarkMockView view = new CreateBookmarkMockView();
CreateBookmarkPresenter presenter = new CreateBookmarkPresenter(context, view, bookmarkService);
presenter.initialize();
presenter.setOnClose(() -> {
close.set(true);
});
view.createAction.execute("z");
NotificationMockView notifyView = notifyViewRef.get();
assertFalse(close.get());
assertNotNull(notifyView);
assertEquals(NotificationType.ERROR, notifyView.type);
assertEquals("bookmark.assign.warning", notifyView.title);
assertEquals("bookmark.exists", notifyView.message);
}
@Test
void testDeleteBookmark() throws BookmarkException {
Bookmark a = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark z = bookmarkService.createBookmark("z");
CreateBookmarkMockView view = new CreateBookmarkMockView() {
@ -161,6 +217,35 @@ class CreateBookmarkPresenterTest extends PresenterTest {
assertEquals(0, bookmarkService.getBookmarks().size());
}
@Test
void testDeleteDefaultBookmark() throws BookmarkException {
Bookmark l1 = bookmarkService.createDefaultBookmark();
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark l2 = bookmarkService.createDefaultBookmark();
CreateBookmarkMockView view = new CreateBookmarkMockView() {
@Override
public void removeBookmark(Bookmark bookmark) {
if (bookmarkService.getBookmarks().size() == 1) {
assertEquals(l1, bookmark);
}
else {
assertEquals(l2, bookmark);
}
}
};
CreateBookmarkPresenter presenter = new CreateBookmarkPresenter(context, view, bookmarkService);
presenter.initialize();
view.deleteAction.execute(l1);
assertEquals(1, bookmarkService.getBookmarks().size());
view.deleteAction.execute(l2);
assertEquals(0, bookmarkService.getBookmarks().size());
}
private static class CreateBookmarkMockView implements CreateBookmarkView {

View file

@ -44,17 +44,20 @@ class GotoBookmarkPresenterTest extends PresenterTest {
private BookmarkService bookmarkService;
private DocumentService documentService;
@BeforeEach
void setup() throws IOException {
Document document = new Document();
document.createPage();
document.createPage();
document.createPage();
DocumentService documentService = new DocumentService(context);
documentService = new DocumentService(context);
documentService.addDocument(document);
documentService.selectDocument(document);
bookmarkService = new BookmarkService(documentService);
bookmarkService = new BookmarkService(documentService, context);
}
@Test
@ -67,13 +70,16 @@ class GotoBookmarkPresenterTest extends PresenterTest {
}
};
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, bookmarkService);
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, documentService, bookmarkService);
presenter.initialize();
}
@Test
void testBookmarkList() throws BookmarkException {
Bookmark a = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark z = bookmarkService.createBookmark("z");
GotoBookmarkMockView view = new GotoBookmarkMockView() {
@ -87,7 +93,7 @@ class GotoBookmarkPresenterTest extends PresenterTest {
}
};
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, bookmarkService);
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, documentService, bookmarkService);
presenter.initialize();
}
@ -99,7 +105,7 @@ class GotoBookmarkPresenterTest extends PresenterTest {
GotoBookmarkMockView view = new GotoBookmarkMockView();
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, bookmarkService);
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, documentService, bookmarkService);
presenter.initialize();
presenter.setOnClose(() -> {
close.set(true);
@ -118,7 +124,7 @@ class GotoBookmarkPresenterTest extends PresenterTest {
GotoBookmarkMockView view = new GotoBookmarkMockView();
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, bookmarkService);
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, documentService, bookmarkService);
presenter.initialize();
presenter.setOnClose(() -> {
close.set(true);
@ -138,6 +144,9 @@ class GotoBookmarkPresenterTest extends PresenterTest {
@Test
void testDeleteBookmark() throws BookmarkException {
Bookmark a = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark z = bookmarkService.createBookmark("z");
GotoBookmarkMockView view = new GotoBookmarkMockView() {
@ -152,7 +161,7 @@ class GotoBookmarkPresenterTest extends PresenterTest {
}
};
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, bookmarkService);
GotoBookmarkPresenter presenter = new GotoBookmarkPresenter(context, view, documentService, bookmarkService);
presenter.initialize();
view.deleteAction.execute(a);
@ -162,6 +171,32 @@ class GotoBookmarkPresenterTest extends PresenterTest {
assertEquals(0, bookmarkService.getBookmarks().size());
}
@Test
void testGoToNextBookmark() throws BookmarkException {
Bookmark a = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark z = bookmarkService.createBookmark("z");
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark y = bookmarkService.createBookmark("y");
documentService.getDocuments().getSelectedDocument().selectPage(0);
assertEquals(bookmarkService.getPageBookmark(), a);
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
assertEquals(bookmarkService.getPageBookmark(), z);
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
assertEquals(bookmarkService.getPageBookmark(), y);
}
private static class GotoBookmarkMockView implements GotoBookmarkView {
@ -171,6 +206,11 @@ class GotoBookmarkPresenterTest extends PresenterTest {
ConsumerAction<Bookmark> deleteAction;
@Override
public void setDocument(Document document) {
}
@Override
public void setBookmarks(List<Bookmark> bookmarkList) {
@ -186,6 +226,11 @@ class GotoBookmarkPresenterTest extends PresenterTest {
}
@Override
public void setOnGotoPageNumber(ConsumerAction<Integer> action) {
}
@Override
public void setOnDeleteBookmark(ConsumerAction<Bookmark> action) {
assertNotNull(action);

View file

@ -121,7 +121,7 @@ class MainPresenterTest extends PresenterTest {
documentService = context.getDocumentService();
bookmarkService = new BookmarkService(documentService);
bookmarkService = new BookmarkService(documentService, context);
recorder = new FileLectureRecorder(audioSystemProvider, documentService, audioConfig, getRecordingDirectory());
@ -200,7 +200,7 @@ class MainPresenterTest extends PresenterTest {
public <T> T getInstance(Class<T> cls) {
if (cls == SlidesPresenter.class) {
ToolController toolController = new ToolController(context, documentService);
return (T) new SlidesPresenter(context, createProxy(SlidesView.class), null, toolController, presentationController, null, documentService, documentRecorder, recordingService, webService, webServiceInfo, streamService);
return (T) new SlidesPresenter(context, createProxy(SlidesView.class), null, toolController, presentationController, null, bookmarkService, documentService, documentRecorder, recordingService, webService, webServiceInfo, streamService);
}
else if (cls == SettingsPresenter.class) {
return (T) new SettingsPresenter(context, createProxy(SettingsView.class));

View file

@ -39,17 +39,23 @@ class BookmarkServiceTest extends ServiceTest {
@BeforeEach
void setUp() {
bookmarkService = new BookmarkService(documentService);
bookmarkService = new BookmarkService(documentService, context);
}
@Test
void testClearBookmarks() throws BookmarkException {
bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
bookmarkService.createBookmark("b");
documentService.selectDocument(document2);
bookmarkService.createBookmark("c");
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
bookmarkService.createBookmark("d");
bookmarkService.clearBookmarks();
@ -67,12 +73,18 @@ class BookmarkServiceTest extends ServiceTest {
@Test
void testClearDocumentBookmarks() throws BookmarkException {
bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
bookmarkService.createBookmark("b");
bookmarkService.clearBookmarks(document1);
documentService.selectDocument(document2);
bookmarkService.createBookmark("a");
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
bookmarkService.createBookmark("b");
assertEquals(2, bookmarkService.getBookmarks().getAllBookmarks().size());
@ -87,6 +99,9 @@ class BookmarkServiceTest extends ServiceTest {
@Test
void testCreateBookmark() throws BookmarkException {
Bookmark bookmarkA = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark bookmarkB = bookmarkService.createBookmark("b");
assertNotNull(bookmarkA);
@ -100,6 +115,9 @@ class BookmarkServiceTest extends ServiceTest {
documentService.selectDocument(document2);
bookmarkA = bookmarkService.createBookmark("a");
currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
bookmarkB = bookmarkService.createBookmark("b");
assertNotNull(bookmarkA);
@ -110,6 +128,9 @@ class BookmarkServiceTest extends ServiceTest {
@Test
void testDeleteBookmark() throws BookmarkException {
Bookmark bookmarkA = bookmarkService.createBookmark("a");
int currPage = documentService.getDocuments().getSelectedDocument().getCurrentPage().getPageNumber();
int maxPages = documentService.getDocuments().getSelectedDocument().getPageCount();
documentService.getDocuments().getSelectedDocument().selectPage((currPage + 1) % maxPages);
Bookmark bookmarkB = bookmarkService.createBookmark("b");
bookmarkService.deleteBookmark(bookmarkB);

View file

@ -32,6 +32,7 @@ menu.quiz.embedded = Embedded quizzes
menu.bookmarks = Bookmarks
menu.bookmarks.clear = Delete all bookmarks
menu.bookmarks.add = Bookmark current slide
menu.bookmarks.remove = Remove current slide bookmark
menu.bookmarks.previous = Go back to previous bookmark
menu.bookmarks.goto = Go to bookmark
menu.slide.actions = Slide Links

View file

@ -68,12 +68,12 @@ public class StylusListener implements org.lecturestudio.stylus.StylusListener {
@Override
public void componentResized(ComponentEvent e) {
Rectangle canvasBounds = slideView.getCanvasBounds();
viewBounds.setLocation(canvasBounds.getMinX(), canvasBounds.getMinY());
viewBounds.setSize(canvasBounds.getWidth(), canvasBounds.getHeight());
updateBounds(slideView.getCanvasBounds());
}
});
slideView.addPropertyChangeListener("CanvasBounds", event -> {
updateBounds((Rectangle) event.getNewValue());
});
}
@Override
@ -118,4 +118,9 @@ public class StylusListener implements org.lecturestudio.stylus.StylusListener {
handler.onButtonUp(stylusEvent, viewBounds);
}
private void updateBounds(Rectangle canvasBounds) {
viewBounds.setLocation(canvasBounds.getMinX(), canvasBounds.getMinY());
viewBounds.setSize(canvasBounds.getWidth(), canvasBounds.getHeight());
}
}

View file

@ -20,20 +20,22 @@ package org.lecturestudio.presenter.swing.view;
import static java.util.Objects.isNull;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.MessageFormat;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.inject.Inject;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.Interval;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.presenter.api.model.Bookmark;
@ -47,10 +49,20 @@ import org.lecturestudio.swing.view.ViewPostConstruct;
@SwingView(name = "goto-bookmark")
public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkView {
private final Dictionary dictionary;
private Interval<Integer> slideBounds;
private ConsumerAction<Integer> gotoPageNumberAction;
private ConsumerAction<Bookmark> gotoBookmarkAction;
private ConsumerAction<Bookmark> deleteBookmarkAction;
private JLabel pageNumberLabel;
private JTextField slideNumberTextField;
private JTextField acceleratorTextField;
private JTable bookmarkTableView;
@ -70,8 +82,22 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
};
SwingGotoBookmarkView() {
@Inject
SwingGotoBookmarkView(Dictionary dictionary) {
super();
this.dictionary = dictionary;
this.slideBounds = new Interval<>();
}
@Override
public void setDocument(Document document) {
String message = MessageFormat.format(dictionary.get("goto.slide.bounds"),
1, document.getPageCount());
pageNumberLabel.setText(message);
slideBounds.set(1, document.getPageCount());
}
@Override
@ -95,6 +121,11 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
SwingUtils.bindAction(closeButton, action);
}
@Override
public void setOnGotoPageNumber(ConsumerAction<Integer> action) {
gotoPageNumberAction = action;
}
@Override
public void setOnGotoBookmark(ConsumerAction<Bookmark> action) {
gotoBookmarkAction = action;
@ -105,6 +136,23 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
deleteBookmarkAction = action;
}
private void selectPageNumber() {
String input = slideNumberTextField.getText();
if (input.isEmpty() || input.isBlank()) {
return;
}
int pageNumber = Integer.parseInt(input);
if (slideBounds.contains(pageNumber)) {
executeAction(gotoPageNumberAction, pageNumber - 1);
}
else {
pageNumberLabel.setForeground(Color.RED);
}
}
private void selectBookmark(int row) {
if (row < 0) {
return;
@ -116,6 +164,18 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
executeAction(gotoBookmarkAction, selectedItem);
}
private void findBookmark(String shortcut) {
BookmarkTableModel model = (BookmarkTableModel) bookmarkTableView.getModel();
for (int i = 0; i < model.getRowCount(); i++) {
Bookmark bookmark = model.getItem(i);
if (bookmark.getShortcut().equalsIgnoreCase(shortcut)) {
executeAction(gotoBookmarkAction, bookmark);
}
}
}
@ViewPostConstruct
private void initialize() {
bookmarkTableView.setModel(new BookmarkTableModel(
@ -157,8 +217,7 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
char c = s.charAt(0);
if (Character.isLetter(c) || Character.isDigit(c)) {
Bookmark bookmark = new Bookmark(String.valueOf(c));
executeAction(gotoBookmarkAction, bookmark);
findBookmark(s);
}
}
}
@ -173,8 +232,12 @@ public class SwingGotoBookmarkView extends ContentPane implements GotoBookmarkVi
}
});
acceleratorTextField.addHierarchyListener(e -> {
acceleratorTextField.requestFocusInWindow();
slideNumberTextField.addActionListener(e -> {
selectPageNumber();
});
slideNumberTextField.addHierarchyListener(e -> {
slideNumberTextField.requestFocusInWindow();
});
}
}

View file

@ -35,17 +35,12 @@ import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.IntegerProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.model.RecentDocument;
import org.lecturestudio.core.model.Time;
import org.lecturestudio.core.model.*;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.presenter.api.context.PresenterContext.ParticipantCount;
import org.lecturestudio.presenter.api.model.Bookmark;
import org.lecturestudio.presenter.api.model.Bookmarks;
import org.lecturestudio.presenter.api.model.MessageBarPosition;
import org.lecturestudio.presenter.api.model.*;
import org.lecturestudio.presenter.api.service.QuizWebServiceState;
import org.lecturestudio.presenter.api.view.MenuView;
import org.lecturestudio.swing.util.SwingUtils;
@ -83,50 +78,68 @@ public class SwingMenuView extends JMenuBar implements MenuView {
private JCheckBoxMenuItem fullscreenMenuItem;
private JCheckBoxMenuItem advancedSettingsMenuItem;
private JMenuItem customizeToolbarMenuItem;
private JMenu externalWindowsMenu;
private JCheckBoxMenuItem externalMessagesMenuItem;
private JCheckBoxMenuItem externalParticipantsMenuItem;
private JCheckBoxMenuItem externalSlidePreviewMenuItem;
private JCheckBoxMenuItem externalSpeechMenuItem;
private JCheckBoxMenuItem notesMenuItem;
private JMenu notesPositionMenu;
private JCheckBoxMenuItem externalNotesMenuItem;
private JCheckBoxMenuItem slideNotesMenuItem;
private JMenu noteSlidePositionMenu;
private JMenu messagesPositionMenu;
private JMenu splitNotesPositionMenu;
private JMenu speechPositionMenu;
private JRadioButtonMenuItem speechPositionAbovePreviewMenuItem;
private JRadioButtonMenuItem speechPositionExternalMenuItem;
private JRadioButtonMenuItem splitNotesPositionRightMenuItem;
private JRadioButtonMenuItem splitNotesPositionLeftMenuItem;
private JRadioButtonMenuItem splitNotesPositionNoneMenuItem;
private JRadioButtonMenuItem messagesPositionLeftMenuItem;
private JRadioButtonMenuItem messagesPositionBottomMenuItem;
private JRadioButtonMenuItem messagesPositionRightMenuItem;
private JRadioButtonMenuItem messagesPositionExternalMenuItem;
private JRadioButtonMenuItem notesPositionLeftMenuItem;
private JRadioButtonMenuItem notesPositionBottomMenuItem;
private JRadioButtonMenuItem notesPositionExternalMenuItem;
private JRadioButtonMenuItem noteSlidePositionBelowPreviewMenuItem;
private JRadioButtonMenuItem noteSlidePositionNoneMenuItem;
private JRadioButtonMenuItem noteSlidePositionExternalMenuItem;
private JMenu participantsPositionMenu;
private JRadioButtonMenuItem participantsPositionLeftMenuItem;
private JRadioButtonMenuItem participantsPositionRightMenuItem;
private JRadioButtonMenuItem participantsPositionExternalMenuItem;
private JMenu previewPositionMenu;
private JRadioButtonMenuItem previewPositionLeftMenuItem;
private JRadioButtonMenuItem previewPositionRightMenuItem;
private JRadioButtonMenuItem previewPositionExternalMenuItem;
private JMenuItem newWhiteboardMenuItem;
private JMenuItem newWhiteboardPageMenuItem;
@ -153,6 +166,8 @@ public class SwingMenuView extends JMenuBar implements MenuView {
private JCheckBoxMenuItem showNotesWindowMenuItem;
private JCheckBoxMenuItem showSlideNotesWindowMenuItem;
private JMenuItem selectQuizMenuItem;
private JMenuItem newQuizMenuItem;
@ -169,10 +184,18 @@ public class SwingMenuView extends JMenuBar implements MenuView {
private JMenuItem newBookmarkMenuItem;
private JMenuItem newDefaultBookmarkMenuItem;
private JMenuItem removeBookmarkMenuItem;
private JMenuItem gotoBookmarkMenuItem;
private JMenuItem previousBookmarkMenuItem;
private JMenuItem prevBookmarkMenuItem;
private JMenuItem nextBookmarkMenuItem;
private JMenuItem logMenuItem;
private JMenuItem aboutMenuItem;
@ -219,14 +242,20 @@ public class SwingMenuView extends JMenuBar implements MenuView {
newBookmarkMenuItem.setEnabled(hasDocument);
gotoBookmarkMenuItem.setEnabled(hasDocument);
previousBookmarkMenuItem.setEnabled(hasDocument);
prevBookmarkMenuItem.setEnabled(hasDocument);
nextBookmarkMenuItem.setEnabled(hasDocument);
newDefaultBookmarkMenuItem.setEnabled(hasDocument);
removeBookmarkMenuItem.setEnabled(hasDocument);
startRecordingMenuItem.setEnabled(hasDocument);
enableStreamMenuItem.setEnabled(hasDocument);
enableMessengerMenuItem.setEnabled(hasDocument);
externalWindowsMenu.setEnabled(hasDocument);
messagesPositionMenu.setEnabled(hasDocument);
notesPositionMenu.setEnabled(hasDocument);
participantsPositionMenu.setEnabled(hasDocument);
previewPositionMenu.setEnabled(hasDocument);
noteSlidePositionMenu.setEnabled(hasDocument);
speechPositionMenu.setEnabled(hasDocument);
splitNotesPositionMenu.setEnabled(hasDocument);
});
}
@ -333,172 +362,110 @@ public class SwingMenuView extends JMenuBar implements MenuView {
SwingUtils.bindBidirectional(outlineMenuItem, showProperty);
}
@Override
public void setAdvancedSettings(boolean selected) {
advancedSettingsMenuItem.setSelected(selected);
}
@Override
public void bindFullscreen(BooleanProperty fullscreen) {
SwingUtils.bindBidirectional(fullscreenMenuItem, fullscreen);
}
@Override
public void setOnAdvancedSettings(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(advancedSettingsMenuItem, action);
}
@Override
public void setOnCustomizeToolbar(Action action) {
SwingUtils.bindAction(customizeToolbarMenuItem, action);
}
@Override
public void setExternalMessages(boolean selected, boolean show) {
externalMessagesMenuItem.setSelected(selected);
externalMessagesMenuItem.setText(!selected || show
? dict.get("menu.external.messages")
: dict.get("menu.external.messages.disconnected"));
public void setSpeechPosition(SpeechPosition position) {
switch (position) {
case ABOVE_SLIDE_PREVIEW -> speechPositionAbovePreviewMenuItem.setSelected(true);
case EXTERNAL -> speechPositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setExternalNotes(boolean selected, boolean show) {
externalNotesMenuItem.setSelected(selected);
externalNotesMenuItem.setText(!selected || show
? dict.get("menu.external.notes")
: dict.get("menu.external.notes.disconnected"));
public void setOnSpeechPosition(ConsumerAction<SpeechPosition> action) {
SwingUtils.bindAction(speechPositionAbovePreviewMenuItem, () -> action.execute(SpeechPosition.ABOVE_SLIDE_PREVIEW));
SwingUtils.bindAction(speechPositionExternalMenuItem, () -> action.execute(SpeechPosition.EXTERNAL));
}
@Override
public void setOnExternalMessages(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(externalMessagesMenuItem, action);
public void setMessagesPosition(MessageBarPosition position) {
switch (position) {
case LEFT -> messagesPositionLeftMenuItem.setSelected(true);
case BOTTOM -> messagesPositionBottomMenuItem.setSelected(true);
case RIGHT -> messagesPositionRightMenuItem.setSelected(true);
case EXTERNAL -> messagesPositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setExternalParticipants(boolean selected, boolean show) {
externalParticipantsMenuItem.setSelected(selected);
externalParticipantsMenuItem.setText(!selected || show
? dict.get("menu.external.participants")
: dict.get("menu.external.participants.disconnected"));
public void setOnMessagesPosition(ConsumerAction<MessageBarPosition> action) {
SwingUtils.bindAction(messagesPositionLeftMenuItem, () -> action.execute(MessageBarPosition.LEFT));
SwingUtils.bindAction(messagesPositionBottomMenuItem, () -> action.execute(MessageBarPosition.BOTTOM));
SwingUtils.bindAction(messagesPositionRightMenuItem, () -> action.execute(MessageBarPosition.RIGHT));
SwingUtils.bindAction(messagesPositionExternalMenuItem, () -> action.execute(MessageBarPosition.EXTERNAL));
}
@Override
public void setOnExternalParticipants(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(externalParticipantsMenuItem, action);
public void setSlideNotesPosition(SlideNotesPosition position) {
switch (position) {
case LEFT -> notesPositionLeftMenuItem.setSelected(true);
case BOTTOM -> notesPositionBottomMenuItem.setSelected(true);
case EXTERNAL -> notesPositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setExternalSlidePreview(boolean selected, boolean show) {
externalSlidePreviewMenuItem.setSelected(selected);
externalSlidePreviewMenuItem.setText(!selected || show
? dict.get("menu.external.slide.preview")
: dict.get("menu.external.slide.preview.disconnected"));
public void setOnSlideNotesPosition(ConsumerAction<SlideNotesPosition> action) {
SwingUtils.bindAction(notesPositionLeftMenuItem, () -> action.execute(SlideNotesPosition.LEFT));
SwingUtils.bindAction(notesPositionBottomMenuItem, () -> action.execute(SlideNotesPosition.BOTTOM));
SwingUtils.bindAction(notesPositionExternalMenuItem, () -> action.execute(SlideNotesPosition.EXTERNAL));
}
@Override
public void setOnExternalSlidePreview(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(externalSlidePreviewMenuItem, action);
public void setNoteSlidePosition(NoteSlidePosition position) {
switch (position) {
case NONE -> noteSlidePositionNoneMenuItem.setSelected(true);
case BELOW_SLIDE_PREVIEW -> noteSlidePositionBelowPreviewMenuItem.setSelected(true);
case EXTERNAL -> noteSlidePositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setExternalSpeech(boolean selected, boolean show) {
externalSpeechMenuItem.setSelected(selected);
externalSpeechMenuItem.setText(!selected || show
? dict.get("menu.external.speech")
: dict.get("menu.external.speech.disconnected"));
public void setOnNoteSlidePosition(ConsumerAction<NoteSlidePosition> action) {
SwingUtils.bindAction(noteSlidePositionNoneMenuItem, () -> action.execute(NoteSlidePosition.NONE));
SwingUtils.bindAction(noteSlidePositionBelowPreviewMenuItem, () -> action.execute(NoteSlidePosition.BELOW_SLIDE_PREVIEW));
SwingUtils.bindAction(noteSlidePositionExternalMenuItem, () -> action.execute(NoteSlidePosition.EXTERNAL));
}
@Override
public void setOnExternalSpeech(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(externalSpeechMenuItem, action);
public void setParticipantsPosition(ParticipantsPosition position) {
switch (position) {
case LEFT -> participantsPositionLeftMenuItem.setSelected(true);
case RIGHT -> participantsPositionRightMenuItem.setSelected(true);
case EXTERNAL -> participantsPositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setOnExternalNotes(ConsumerAction<Boolean> action) {
SwingUtils.bindAction(externalNotesMenuItem, action);
public void setOnParticipantsPosition(ConsumerAction<ParticipantsPosition> action) {
SwingUtils.bindAction(participantsPositionLeftMenuItem, () -> action.execute(ParticipantsPosition.LEFT));
SwingUtils.bindAction(participantsPositionRightMenuItem, () -> action.execute(ParticipantsPosition.RIGHT));
SwingUtils.bindAction(participantsPositionExternalMenuItem, () -> action.execute(ParticipantsPosition.EXTERNAL));
}
@Override
public void setOnMessagesPositionLeft(Action action) {
SwingUtils.bindAction(messagesPositionLeftMenuItem, action);
public void setSlidePreviewPosition(SlidePreviewPosition position) {
switch (position) {
case LEFT -> previewPositionLeftMenuItem.setSelected(true);
case RIGHT -> previewPositionRightMenuItem.setSelected(true);
case EXTERNAL -> previewPositionExternalMenuItem.setSelected(true);
}
}
@Override
public void setMessagesPositionLeft() {
messagesPositionLeftMenuItem.setSelected(true);
}
@Override
public void setOnMessagesPositionBottom(Action action) {
SwingUtils.bindAction(messagesPositionBottomMenuItem, action);
}
@Override
public void setMessagesPositionBottom() {
messagesPositionBottomMenuItem.setSelected(true);
}
@Override
public void setOnMessagesPositionRight(Action action) {
SwingUtils.bindAction(messagesPositionRightMenuItem, action);
}
@Override
public void setMessagesPositionRight() {
messagesPositionRightMenuItem.setSelected(true);
}
@Override
public void setOnNotesPositionBottom(Action action) {
SwingUtils.bindAction(notesPositionBottomMenuItem, action);
}
@Override
public void setNotesPositionBottom() {
notesPositionBottomMenuItem.setSelected(true);
}
@Override
public void setOnNotesPositionLeft(Action action) {
SwingUtils.bindAction(notesPositionLeftMenuItem, action);
}
@Override
public void setNotesPositionLeft() {
notesPositionLeftMenuItem.setSelected(true);
}
@Override
public void setOnParticipantsPositionLeft(Action action) {
SwingUtils.bindAction(participantsPositionLeftMenuItem, action);
}
@Override
public void setParticipantsPositionLeft() {
participantsPositionLeftMenuItem.setSelected(true);
}
@Override
public void setOnParticipantsPositionRight(Action action) {
SwingUtils.bindAction(participantsPositionRightMenuItem, action);
}
@Override
public void setParticipantsPositionRight() {
participantsPositionRightMenuItem.setSelected(true);
}
@Override
public void bindPreviewPosition(ObjectProperty<MessageBarPosition> position) {
setPreviewPosition(position.get());
position.addListener((o, oldPos, newPos) -> {
setPreviewPosition(newPos);
});
previewPositionLeftMenuItem.addActionListener(e -> position.set(MessageBarPosition.LEFT));
previewPositionRightMenuItem.addActionListener(e -> position.set(MessageBarPosition.RIGHT));
public void setOnSlidePreviewPosition(ConsumerAction<SlidePreviewPosition> action) {
SwingUtils.bindAction(previewPositionLeftMenuItem, () -> action.execute(SlidePreviewPosition.LEFT));
SwingUtils.bindAction(previewPositionRightMenuItem, () -> action.execute(SlidePreviewPosition.RIGHT));
SwingUtils.bindAction(previewPositionExternalMenuItem, () -> action.execute(SlidePreviewPosition.EXTERNAL));
}
@Override
@ -671,7 +638,7 @@ public class SwingMenuView extends JMenuBar implements MenuView {
public void setBookmarks(Bookmarks bookmarks) {
SwingUtils.invoke(() -> {
List<Bookmark> bookmarkList = bookmarks.getAllBookmarks();
int fixedMenuItems = 5;
int fixedMenuItems = 10;
// Remove all bookmark menu items.
if (bookmarksMenu.getItemCount() > fixedMenuItems) {
@ -716,6 +683,16 @@ public class SwingMenuView extends JMenuBar implements MenuView {
SwingUtils.bindAction(newBookmarkMenuItem, action);
}
@Override
public void setOnCreateNewDefaultBookmarkView(Action action) {
SwingUtils.bindAction(newDefaultBookmarkMenuItem, action);
}
@Override
public void setOnRemoveBookmarkView(Action action) {
SwingUtils.bindAction(removeBookmarkMenuItem, action);
}
@Override
public void setOnShowGotoBookmarkView(Action action) {
SwingUtils.bindAction(gotoBookmarkMenuItem, action);
@ -726,6 +703,16 @@ public class SwingMenuView extends JMenuBar implements MenuView {
SwingUtils.bindAction(previousBookmarkMenuItem, action);
}
@Override
public void setOnPrevBookmark(Action action) {
SwingUtils.bindAction(prevBookmarkMenuItem, action);
}
@Override
public void setOnNextBookmark(Action action) {
SwingUtils.bindAction(nextBookmarkMenuItem, action);
}
@Override
public void setOnOpenBookmark(ConsumerAction<Bookmark> action) {
this.openBookmarkAction = action;
@ -803,6 +790,22 @@ public class SwingMenuView extends JMenuBar implements MenuView {
});
}
@Override
public void setSplitNotesPosition(NotesPosition position) {
switch (position) {
case LEFT -> splitNotesPositionLeftMenuItem.setSelected(true);
case RIGHT -> splitNotesPositionRightMenuItem.setSelected(true);
case NONE -> splitNotesPositionNoneMenuItem.setSelected(true);
}
}
@Override
public void setOnSplitNotesPosition(ConsumerAction<NotesPosition> action) {
SwingUtils.bindAction(splitNotesPositionNoneMenuItem, () -> action.execute(NotesPosition.NONE));
SwingUtils.bindAction(splitNotesPositionLeftMenuItem, () -> action.execute(NotesPosition.LEFT));
SwingUtils.bindAction(splitNotesPositionRightMenuItem, () -> action.execute(NotesPosition.RIGHT));
}
@ViewPostConstruct
private void initialize() {
setStateText(enableStreamMenuItem, dict.get("menu.stream.start"),
@ -814,16 +817,6 @@ public class SwingMenuView extends JMenuBar implements MenuView {
setStateText(enableStreamCameraMenuItem, dict.get("menu.stream.camera.start"),
dict.get("menu.stream.camera.stop"));
// TODO: Creation of ButtonGroup can be removed here and can be set in the view xml.
final ButtonGroup messagesPositionButtonGroup = new ButtonGroup();
messagesPositionButtonGroup.add(messagesPositionLeftMenuItem);
messagesPositionButtonGroup.add(messagesPositionBottomMenuItem);
messagesPositionButtonGroup.add(messagesPositionRightMenuItem);
final ButtonGroup participantsPositionButtonGroup = new ButtonGroup();
participantsPositionButtonGroup.add(participantsPositionLeftMenuItem);
participantsPositionButtonGroup.add(participantsPositionRightMenuItem);
stopwatchMenu.setBorderPainted(false);
stopwatchMenu.setFocusPainted(false);
stopwatchMenu.addMouseListener(new MouseAdapter() {
@ -867,11 +860,4 @@ public class SwingMenuView extends JMenuBar implements MenuView {
recordIndicatorMenu.setText(null);
}
}
private void setPreviewPosition(MessageBarPosition position) {
switch (position) {
case LEFT -> previewPositionLeftMenuItem.setSelected(true);
case RIGHT -> previewPositionRightMenuItem.setSelected(true);
}
}
}

View file

@ -45,6 +45,7 @@ import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import org.lecturestudio.core.ExecutableState;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.controller.ToolController;
import org.lecturestudio.core.graphics.Color;
@ -59,6 +60,7 @@ import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.core.view.PresentationParameter;
import org.lecturestudio.presenter.api.view.ToolbarView;
import org.lecturestudio.swing.AwtResourceLoader;
import org.lecturestudio.swing.components.FontPickerButton;
import org.lecturestudio.swing.components.RecordButton;
import org.lecturestudio.swing.components.ToolColorPickerButton;
@ -96,6 +98,12 @@ public class SwingToolbarView extends JPanel implements ToolbarView {
private JButton nextSlideButton;
private JButton prevBookmarkButton;
private JButton nextBookmarkButton;
private JButton newBookmarkButton;
private ToolColorPickerButton customColorButton;
private JToggleButton colorButton1;
@ -168,11 +176,19 @@ public class SwingToolbarView extends JPanel implements ToolbarView {
private CustomizedToolbar customizedToolbar;
private final Dictionary dict;
private static final String REMOVE_BOOKMARK_KEY = "menu.bookmarks.remove";
private static final String ADD_BOOKMARK_KEY = "menu.bookmarks.add";
@Inject
SwingToolbarView(ResourceBundle resourceBundle, ToolController toolController) {
SwingToolbarView(Dictionary dict, ResourceBundle resourceBundle, ToolController toolController) {
super();
this.dict = dict;
this.resourceBundle = resourceBundle;
this.toolController = toolController;
@ -271,6 +287,23 @@ public class SwingToolbarView extends JPanel implements ToolbarView {
SwingUtils.bindAction(nextSlideButton, action);
}
@Override
public void setOnPreviousBookmark(Action action) {
SwingUtils.bindAction(prevBookmarkButton, action);
}
@Override
public void setOnNextBookmark(Action action) {
SwingUtils.bindAction(nextBookmarkButton, action);
}
@Override
public void setOnNewBookmark(Action action) {
SwingUtils.bindAction(newBookmarkButton, action);
}
@Override
public void setOnCustomPaletteColor(ConsumerAction<Color> action) {
this.paletteColorAction = action;
@ -521,6 +554,17 @@ public class SwingToolbarView extends JPanel implements ToolbarView {
});
}
@Override
public void selectNewBookmarkButton(boolean hasBookmark){
if (hasBookmark){
newBookmarkButton.setIcon(AwtResourceLoader.getIcon("bookmark-remove.svg", 24));
newBookmarkButton.setToolTipText(dict.get(REMOVE_BOOKMARK_KEY));
} else {
newBookmarkButton.setIcon(AwtResourceLoader.getIcon("bookmark-add.svg", 24));
newBookmarkButton.setToolTipText(dict.get(ADD_BOOKMARK_KEY));
}
}
@Override
public void selectToolButton(ToolType toolType) {
SwingUtils.invoke(() -> {

View file

@ -35,10 +35,10 @@ public class BookmarkTableModel extends TableModelBase<Bookmark> {
switch (columnIndex) {
case 0:
return bookmark.getPage().getDocument().getTitle();
return bookmark.getPage().getDocument().getName();
case 1:
return bookmark.getPage().getPageNumber();
return bookmark.getPage().getPageNumber() + 1;
case 2:
return bookmark.getShortcut();

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
d="m 50,32.500285 a 2.1875353,2.1875353 0 0 1 2.187535,2.187535 v 6.562606 h 6.562606 a 2.1875353,2.1875353 0 0 1 0,4.37507 h -6.562606 v 6.562606 a 2.1875355,2.1875355 0 0 1 -4.375071,0 v -6.562606 h -6.562605 a 2.1875353,2.1875353 0 0 1 0,-4.37507 h 6.562605 V 34.68782 A 2.1875353,2.1875353 0 0 1 50,32.500285 Z M 23.749576,23.750143 a 8.7501413,8.7501413 0 0 1 8.750141,-8.750141 h 35.000565 a 8.7501413,8.7501413 0 0 1 8.750142,8.750141 v 59.06345 a 2.1875353,2.1875353 0 0 1 -3.39943,1.82003 L 50,72.317802 27.149006,84.633623 a 2.1875353,2.1875353 0 0 1 -3.39943,-1.82003 z m 8.750141,-4.37507 a 4.3750706,4.3750706 0 0 0 -4.37507,4.37507 v 54.97714 L 48.788105,67.868355 a 2.1875353,2.1875353 0 0 1 2.423789,0 l 20.663459,10.858928 v -54.97714 a 4.3750706,4.3750706 0 0 0 -4.375071,-4.37507 z" />
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
d="M 42.908645,58.410205 V 33.589796 l 14.182732,12.410203 z m 1.705224,1.948471 14.180157,-12.410191 a 2.5876182,2.5876182 0 0 0 0,-3.89697 L 44.613869,31.641324 a 2.5876182,2.5876182 0 0 0 -4.292817,1.948472 v 24.820409 a 2.5876182,2.5876182 0 0 0 4.292817,1.948471 z M 23.749576,23.750141 A 8.7501413,8.7501413 0 0 1 32.499717,15 h 35.000565 a 8.7501413,8.7501413 0 0 1 8.750142,8.750141 v 59.063454 a 2.1875353,2.1875353 0 0 1 -3.39943,1.820029 L 50,72.3178 27.149006,84.633624 a 2.1875353,2.1875353 0 0 1 -3.39943,-1.820029 z m 8.750141,-4.37507 a 4.3750706,4.3750706 0 0 0 -4.37507,4.37507 V 78.727279 L 48.788105,67.868353 a 2.1875353,2.1875353 0 0 1 2.423789,0 L 71.875353,78.727279 V 23.750141 a 4.3750706,4.3750706 0 0 0 -4.375071,-4.37507 z" />
</svg>

After

Width:  |  Height:  |  Size: 990 B

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
d="M 57.091355,58.410205 V 33.589796 L 42.908623,45.999999 Z M 55.386131,60.358676 41.205974,47.948485 a 2.5876182,2.5876182 0 0 1 0,-3.89697 L 55.386131,31.641324 a 2.5876182,2.5876182 0 0 1 4.292817,1.948472 v 24.820409 a 2.5876182,2.5876182 0 0 1 -4.292817,1.948471 z M 23.749576,23.750141 A 8.7501413,8.7501413 0 0 1 32.499717,15 h 35.000565 a 8.7501413,8.7501413 0 0 1 8.750142,8.750141 v 59.063454 a 2.1875353,2.1875353 0 0 1 -3.39943,1.820029 L 50,72.3178 27.149006,84.633624 a 2.1875353,2.1875353 0 0 1 -3.39943,-1.820029 z m 8.750141,-4.37507 a 4.3750706,4.3750706 0 0 0 -4.37507,4.37507 V 78.727279 L 48.788105,67.868353 a 2.1875353,2.1875353 0 0 1 2.423789,0 L 71.875353,78.727279 V 23.750141 a 4.3750706,4.3750706 0 0 0 -4.375071,-4.37507 z" />
</svg>

After

Width:  |  Height:  |  Size: 991 B

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
d="M 23.749575,23.750142 A 8.7501418,8.7501418 0 0 1 32.499715,15 H 67.50028 a 8.7501418,8.7501418 0 0 1 8.750146,8.750142 v 59.063457 a 2.1875354,2.1875354 0 0 1 -3.399431,1.820025 L 50,72.317804 27.149004,84.633624 a 2.1875354,2.1875354 0 0 1 -3.399429,-1.820025 z m 8.750142,-4.375071 a 4.3750709,4.3750709 0 0 0 -4.375072,4.375071 v 54.977141 l 20.66346,-10.858926 a 2.1875354,2.1875354 0 0 1 2.423789,0 L 71.875348,78.727283 V 23.750142 A 4.3750709,4.3750709 0 0 0 67.50028,19.375071 Z m 6.562605,24.062889 a 2.1875354,2.1875354 0 0 1 2.187537,-2.187535 H 58.75014 a 2.1875355,2.1875355 0 0 1 0,4.375071 H 41.249859 A 2.1875354,2.1875354 0 0 1 39.062322,43.43796 Z" />
</svg>

After

Width:  |  Height:  |  Size: 908 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M 17.691 40.129 A 2.692 2.692 0 0 0 15 42.82 A 2.692 2.692 0 0 0 17.691 45.514 H 67.95 A 2.692 2.692 0 0 0 70.642 42.82 A 2.692 2.692 0 0 0 67.95 40.129 H 17.691 Z M 17.691 25.77 A 2.692 2.692 0 0 0 15 28.46 A 2.692 2.692 0 0 0 17.691 31.154 H 82.31 A 2.692 2.692 0 0 0 85 28.461 A 2.692 2.692 0 0 0 82.309 25.77 H 17.69 Z M 17.691 54.486 A 2.692 2.692 0 0 0 15 57.18 A 2.692 2.692 0 0 0 17.691 59.871 H 82.31 A 2.692 2.692 0 0 0 85 57.18 A 2.692 2.692 0 0 0 82.309 54.486 H 17.69 Z M 17.691 68.846 A 2.692 2.692 0 0 0 15 71.539 A 2.692 2.692 0 0 0 17.691 74.23 H 67.95 A 2.692 2.692 0 0 0 70.642 71.54 A 2.692 2.692 0 0 0 67.95 68.846 H 17.691 Z" />
</svg>

After

Width:  |  Height:  |  Size: 732 B

View file

@ -1,14 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<Panel name="goto.bookmark.title">
<Panel layout="GridBagLayout" border="EmptyBorder(0, 20, 20, 20)">
<Label text="goto.bookmark.key" border="EmptyBorder(0, 0, 5, 0)">
<Label text="goto.slide.title" border="EmptyBorder(0, 0, 5, 0)">
<gridbagconstraints gridx="0" gridy="0" fill="GridBagConstraints.BOTH" />
</Label>
<TextField id="acceleratorTextField" text="">
<TextField id="slideNumberTextField" text="">
<gridbagconstraints gridx="0" gridy="1" fill="GridBagConstraints.BOTH" />
<Filter class="org.lecturestudio.swing.filter.IntFilter" />
</TextField>
<Label id="pageNumberLabel" border="EmptyBorder(3, 0, 5, 0)">
<gridbagconstraints gridx="0" gridy="2" fill="GridBagConstraints.BOTH" />
</Label>
<Label text="goto.bookmark.key" border="EmptyBorder(15, 0, 5, 0)">
<gridbagconstraints gridx="0" gridy="3" fill="GridBagConstraints.BOTH" />
</Label>
<TextField id="acceleratorTextField" text="">
<gridbagconstraints gridx="0" gridy="4" fill="GridBagConstraints.BOTH" />
</TextField>
<ScrollPane border="EmptyBorder(10, 0, 0, 0)">
<gridbagconstraints gridx="0" gridy="2" weightx="1.0" weighty="1.0" fill="GridBagConstraints.BOTH" />
<gridbagconstraints gridx="0" gridy="5" weightx="1.0" weighty="1.0" fill="GridBagConstraints.BOTH" />
<Table id="bookmarkTableView" autoCreateColumnsFromModel="false" fillsViewportHeight="true" rowHeight="25" selectionMode="ListSelectionModel.SINGLE_SELECTION">
<TableColumn headerValue="goto.bookmark.document" />
<TableColumn headerValue="goto.bookmark.slide.number" maxWidth="65" />
@ -20,7 +30,7 @@
</Table>
</ScrollPane>
<HBox gap="5" border="EmptyBorder(10, 0, 0, 0)">
<gridbagconstraints gridx="0" gridy="3" fill="GridBagConstraints.HORIZONTAL" />
<gridbagconstraints gridx="0" gridy="6" fill="GridBagConstraints.HORIZONTAL" />
<box.hglue />
<Button id="closeButton" text="button.close"/>
</HBox>

View file

@ -1,5 +1,8 @@
goto.bookmark.title = Lesezeichen anspringen
goto.bookmark.title = Folien und Lesezeichen anspringen
goto.bookmark.document = Dokument
goto.bookmark.key = Taste
goto.bookmark.key = Lesezeichentaste
goto.bookmark.slide.number = Folien #
goto.bookmark.delete = Lesezeichen l\u00f6schen
goto.bookmark.delete = Lesezeichen l\u00f6schen
goto.slide.title = Foliennummer
goto.slide.bounds = Folie {0} bis {1}

View file

@ -1,5 +1,8 @@
goto.bookmark.title = Navigate to bookmarks
goto.bookmark.title = Navigate to slides and bookmarks
goto.bookmark.document = Document
goto.bookmark.key = Key
goto.bookmark.key = Bookmark key
goto.bookmark.slide.number = Slide #
goto.bookmark.delete = Delete bookmark
goto.bookmark.delete = Delete bookmark
goto.slide.title = Slide number
goto.slide.bounds = Slide {0} to {1}

View file

@ -15,39 +15,58 @@
<menuitem id="settingsMenuItem" text="menu.options" />
</menu>
<menu text="menu.view">
<checkboxmenuitem id="outlineMenuItem" text="menu.contents" />
<checkboxmenuitem id="fullscreenMenuItem" text="menu.fullscreen" type="checkbox" accelerator="alt ENTER" />
<menuitem id="customizeToolbarMenuItem" text="menu.toolbar.customize" />
<menu id="externalWindowsMenu" text="menu.external.windows">
<checkboxmenuitem id="externalMessagesMenuItem" text="menu.external.messages" />
<checkboxmenuitem id="externalParticipantsMenuItem" text="menu.external.participants" />
<checkboxmenuitem id="externalSlidePreviewMenuItem" text="menu.external.slide.preview" />
<checkboxmenuitem id="externalSpeechMenuItem" text="menu.external.speech" />
<checkboxmenuitem id="externalNotesMenuItem" text="menu.external.notes" />
</menu>
<checkboxmenuitem id="outlineMenuItem" text="menu.contents" />
<separator />
<menu id="messagesPositionMenu" text="menu.messages.position">
<buttongroup>
<radiobuttonmenuitem id="messagesPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="messagesPositionBottomMenuItem" text="menu.bottom" />
<radiobuttonmenuitem id="messagesPositionRightMenuItem" text="menu.right" />
</buttongroup>
</menu>
<menu id="notesPositionMenu" text="menu.notes.position">
<buttongroup>
<radiobuttonmenuitem id="notesPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="notesPositionBottomMenuItem" text="menu.bottom" />
<radiobuttonmenuitem id="messagesPositionBottomMenuItem" text="menu.bottom" />
<radiobuttonmenuitem id="messagesPositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<menu id="participantsPositionMenu" text="menu.participants.position">
<buttongroup>
<radiobuttonmenuitem id="participantsPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="participantsPositionRightMenuItem" text="menu.right" />
<radiobuttonmenuitem id="participantsPositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<menu id="previewPositionMenu" text="menu.preview.position">
<buttongroup>
<radiobuttonmenuitem id="previewPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="previewPositionRightMenuItem" text="menu.right" />
<radiobuttonmenuitem id="previewPositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<menu id="notesPositionMenu" text="menu.notes.position">
<buttongroup>
<radiobuttonmenuitem id="notesPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="notesPositionBottomMenuItem" text="menu.bottom" />
<radiobuttonmenuitem id="notesPositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<menu id="noteSlidePositionMenu" text="menu.note.slide.position">
<buttongroup>
<radiobuttonmenuitem id="noteSlidePositionBelowPreviewMenuItem" text="menu.note.slide.below.preview" />
<radiobuttonmenuitem id="noteSlidePositionNoneMenuItem" text="menu.none" />
<radiobuttonmenuitem id="noteSlidePositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<menu id="speechPositionMenu" text="menu.speech.position">
<buttongroup>
<radiobuttonmenuitem id="speechPositionAbovePreviewMenuItem" text="menu.speech.position.above.preview" />
<radiobuttonmenuitem id="speechPositionExternalMenuItem" text="menu.external" />
</buttongroup>
</menu>
<separator />
<menu id="splitNotesPositionMenu" text="menu.splitNotes.position">
<buttongroup>
<radiobuttonmenuitem id="splitNotesPositionLeftMenuItem" text="menu.left" />
<radiobuttonmenuitem id="splitNotesPositionRightMenuItem" text="menu.right" />
<radiobuttonmenuitem id="splitNotesPositionNoneMenuItem" text="menu.none" />
</buttongroup>
</menu>
</menu>
@ -69,7 +88,6 @@
<checkboxmenuitem id="enableStreamCameraMenuItem" text="menu.stream.camera.start" type="checkbox" selected="true" enabled="false" />
<separator />
<checkboxmenuitem id="enableMessengerMenuItem" text="menu.messenger.start" enabled="false" />
<!-- <checkboxmenuitem id="showMessengerWindowMenuItem" text="menu.messenger.window.visible" type="checkbox" selected="true" enabled="false" />-->
<separator />
<menuitem id="selectQuizMenuItem" text="menu.quiz.selection" accelerator="Z" enabled="false" />
<menuitem id="newQuizMenuItem" text="menu.quiz.new" enabled="false" />
@ -82,9 +100,14 @@
<menu id="bookmarksMenu" text="menu.bookmarks">
<menuitem id="clearBookmarksMenuItem" text="menu.bookmarks.clear" enabled="false" />
<menuitem id="newBookmarkMenuItem" text="menu.bookmarks.add" accelerator="B" enabled="false" />
<menuitem id="removeBookmarkMenuItem" text="menu.bookmarks.remove" enabled="false" />
<menuitem id="newDefaultBookmarkMenuItem" text="menu.bookmarks.add.default" enabled="false" />
<separator />
<menuitem id="gotoBookmarkMenuItem" text="menu.bookmarks.goto" accelerator="G" enabled="false" />
<menuitem id="previousBookmarkMenuItem" text="menu.bookmarks.previous" accelerator="L" enabled="false" />
<separator />
<menuitem id="prevBookmarkMenuItem" text="menu.bookmarks.prev" enabled="false" />
<menuitem id="nextBookmarkMenuItem" text="menu.bookmarks.next" enabled="false" />
</menu>
<menu text="menu.info">
<menuitem id="logMenuItem" text="menu.log" />

View file

@ -13,9 +13,11 @@ menu.participants = Teilnehmer:innen
menu.fullscreen = Vollbild
menu.advanced.settings = Erweiterte Einstellungen
menu.toolbar.customize = Toolbar anpassen
menu.external.windows = Externe Fenster
menu.external.window = Externes Fenster
menu.external.notes = Notizen
menu.external.notes.disconnected = Notizen (Bildschirm getrennt)
menu.external.slide.notes = Notiz-Folien
menu.external.slide.notes.disconnected = Notiz-Folien (Bildschirm getrennt)
menu.external.messages = Nachrichten
menu.external.messages.disconnected = Nachrichten (Bildschirm getrennt)
menu.external.participants = Teilnehmer:innen
@ -26,11 +28,17 @@ menu.external.speech = Redebeitrag
menu.external.speech.disconnected = Redebeitrag (Bildschirm getrennt)
menu.messages.position = Position der Nachrichten
menu.notes.position = Position der Notizen
menu.note.slide.position = Position der Notiz-Folien
menu.note.slide.below.preview = Unter der Folienvorschau
menu.participants.position = Position der Teilnehmer:innen
menu.preview.position = Position der Folienvorschau
menu.speech.position = Position des Redebeitrags
menu.speech.position.above.preview = \u00DCber der Folienvorschau
menu.left = Links
menu.bottom = Unten
menu.right = Rechts
menu.external = Extern
menu.none = Ohne
menu.whiteboard = Whiteboard
menu.whiteboard.new = Whiteboard erstellen
menu.grid = Gitternetz ein/ausschalten
@ -60,11 +68,16 @@ menu.quiz.embedded = Eingebettete Quizzes
menu.bookmarks = Lesezeichen
menu.bookmarks.clear = Alle Lesezeichen l\u00F6schen
menu.bookmarks.add = Aktuelle Folie als Lesezeichen
menu.bookmarks.remove = Lesezeichen auf aktueller Folie l\u00F6schen
menu.bookmarks.add.default = Aktuelle Folie als Lesezeichen (Standard-K\u00FCrzel)
menu.bookmarks.previous = Zur\u00FCck zur letzten Position
menu.bookmarks.goto = Lesezeichen anspringen
menu.slide.actions = Folienlinks
menu.log = Log
menu.about = \u00DCber
menu.stopwatch.stop = Stoppuhr zurücksetzen
menu.stopwatch.stop = Stoppuhr zur\u00FCcksetzen
menu.stopwatch.pause = Stoppuhr Starten/Anhalten
menu.stopwatch.config = Stoppuhr Einstellungen
menu.stopwatch.config = Stoppuhr Einstellungen
menu.bookmarks.prev = Zum vorherigen Lesezeichen springen
menu.bookmarks.next = Zum n\u00E4chsten Lesezeichen springen
menu.splitNotes.position = Notiz-Folie

View file

@ -13,8 +13,9 @@ menu.participants = Participants
menu.fullscreen = Fullscreen
menu.advanced.settings = Advanced settings
menu.toolbar.customize = Customize toolbar
menu.external.windows = External windows
menu.external.window = External window
menu.external.notes = Notes
menu.external.slide.notes = Slide notes
menu.external.notes.disconnected = Notes (Display disconnected)
menu.external.messages = Messages
menu.external.messages.disconnected = Messages (Display disconnected)
@ -26,11 +27,17 @@ menu.external.speech = Speech
menu.external.speech.disconnected = Speech (Display disconnected)
menu.messages.position = Messages position
menu.notes.position = Notes position
menu.note.slide.position = Slide note position
menu.note.slide.below.preview = Below the slide preview
menu.participants.position = Participants position
menu.preview.position = Slide preview position
menu.speech.position = Speech position
menu.speech.position.above.preview = Above the slide preview
menu.left = Left
menu.bottom = Bottom
menu.right = Right
menu.external = External
menu.none = None
menu.whiteboard = Whiteboard
menu.whiteboard.new = Create whiteboard
menu.grid = Switch grid on/off
@ -60,6 +67,8 @@ menu.quiz.embedded = Embedded quizzes
menu.bookmarks = Bookmarks
menu.bookmarks.clear = Delete all bookmarks
menu.bookmarks.add = Bookmark current slide
menu.bookmarks.remove = Remove bookmark current slide
menu.bookmarks.add.default = Bookmark current slide (default shortcut)
menu.bookmarks.previous = Go back to previous bookmark
menu.bookmarks.goto = Go to bookmark
menu.slide.actions = Slide Links
@ -68,3 +77,6 @@ menu.about = About
menu.stopwatch.stop = reset stopwatch
menu.stopwatch.pause = start/pause stopwatch
menu.stopwatch.config = Stopwatch configuration
menu.bookmarks.prev = Go to previous bookmark
menu.bookmarks.next = Go to next bookmark
menu.splitNotes.position = Notes slide

View file

@ -4,20 +4,24 @@
<SplitPane id="tabSplitPane" dividerSize="10" resizeWeight="1" orientation="JSplitPane.HORIZONTAL_SPLIT" constraints="BorderLayout.CENTER">
<SplitPane id="notesSplitPane" dividerSize="10" resizeWeight="1" orientation="JSplitPane.VERTICAL_SPLIT">
<SplitPane id="docSplitPane" dividerSize="10" resizeWeight="0" focusable="false" orientation="JSplitPane.HORIZONTAL_SPLIT">
<AdaptiveTabbedPane id="leftTabPane" tabPlacement="JTabbedPane.LEFT" >
<Tab id="participantsTab" name="participants" text="slides.participants" icon="participants.svg, 18" defaultTabType="AdaptiveTabType.PARTICIPANTS">
<Panel id="participantsPanel" layout="BorderLayout">
<ParticipantList id="participantList" constraints="BorderLayout.CENTER" />
</Panel>
</Tab>
<Tab id="outlineTab" name="outline" text="menu.contents">
<ScrollPane id="outlinePane">
<Tree id="outlineTree" name="tree" focusable="false" rootVisible="false" showsRootHandles="true">
<renderer class="org.lecturestudio.swing.tree.OutlineRenderer" />
</Tree>
</ScrollPane>
</Tab>
</AdaptiveTabbedPane>
<VBox id="leftVbox">
<Panel layout="BorderLayout" id="leftPeerViewContainer" visible="false" />
<AdaptiveTabbedPane id="leftTabPane" tabPlacement="JTabbedPane.LEFT" >
<Tab id="participantsTab" name="participants" text="slides.participants" icon="participants.svg, 18" defaultTabType="AdaptiveTabType.PARTICIPANTS">
<Panel id="participantsPanel" layout="BorderLayout">
<ParticipantList id="participantList" constraints="BorderLayout.CENTER" />
</Panel>
</Tab>
<Tab id="outlineTab" name="outline" text="menu.contents">
<ScrollPane id="outlinePane">
<Tree id="outlineTree" name="tree" focusable="false" rootVisible="false" showsRootHandles="true">
<renderer class="org.lecturestudio.swing.tree.OutlineRenderer" />
</Tree>
</ScrollPane>
</Tab>
</AdaptiveTabbedPane>
<Panel layout="BorderLayout" id="leftNoteSlideViewContainer" visible="false" />
</VBox>
<SlideView id="slideView" />
</SplitPane>
@ -48,8 +52,11 @@
</SplitPane>
<VBox id="rightVbox">
<Panel layout="BorderLayout" id="peerViewContainer" visible="false" />
<Panel layout="BorderLayout" id="rightPeerViewContainer" visible="false" />
<AdaptiveTabbedPane id="rightTabPane" tabPlacement="JTabbedPane.RIGHT" defaultTabType="AdaptiveTabType.SLIDE" focusTraversable="false" />
<Panel layout="BorderLayout" id="rightNoteSlideViewContainer" visible="false">
<SlideView id="slideNotesView" />
</Panel>
</VBox>
</SplitPane>
</Panel>

View file

@ -11,6 +11,7 @@ slides.slide.preview = Folienvorschau
slides.speech = Redebeitrag
slides.currently.no.speech = Aktuell kein Redebeitrag
slides.quiz.stop = Beenden
slides.slide.notes = Notiz-Folien
speech.requested = hat sich gemeldet
speech.accept = Annehmen

View file

@ -11,6 +11,7 @@ slides.slide.preview = Slides preview
slides.speech = Speech
slides.currently.no.speech = Currently no speach
slides.quiz.stop = Stop
slides.slide.notes = Slide notes
speech.requested = raised his hand
speech.accept = Accept

View file

@ -5,6 +5,9 @@
<Separator type="t" defaultTool="true" />
<Button id="prevSlideButton" icon="prev-slide-tool.svg, 24" tooltipText="toolbar.slide.prev.tooltip" />
<Button id="nextSlideButton" icon="next-slide-tool.svg, 24" tooltipText="toolbar.slide.next.tooltip" />
<Button id="nextBookmarkButton" icon="bookmark-next.svg, 24" tooltipText="toolbar.bookmark.next.tooltip" />
<Button id="prevBookmarkButton" icon="bookmark-prev.svg, 24" tooltipText="toolbar.bookmark.prev.tooltip" />
<Button id="newBookmarkButton" icon="bookmark-add.svg, 24" tooltipText="toolbar.bookmark.add.tooltip" tool="BOOKMARK" group="toolGroup" visible="false" />
<ToolColorPickerButton id="customColorButton" accelerator="F1" group="colorGroup" />
<ToggleButton id="colorButton1" accelerator="F2" group="colorGroup" defaultTool="true"/>
<ToggleButton id="colorButton2" accelerator="F3" group="colorGroup" defaultTool="true" />
@ -15,7 +18,7 @@
<ToggleButton id="penButton" accelerator="P" icon="pen-tool.svg, 24" tooltipText="toolbar.pen.tooltip" tool="PEN" group="toolGroup" defaultTool="true" />
<ToggleButton id="highlighterButton" accelerator="H" icon="highlighter-tool.svg, 24" tooltipText="toolbar.highlighter.tooltip" tool="HIGHLIGHTER" group="toolGroup" defaultTool="true" />
<ToggleButton id="pointerButton" accelerator="A" icon="pointer-tool.svg, 24" tooltipText="toolbar.pointer.tooltip" tool="POINTER" group="toolGroup" defaultTool="true" />
<ToggleButton id="textSelectButton" accelerator="S" icon="text-select-tool.svg, 24" tooltipText="toolbar.text.select.tooltip" tool="TEXT_SELECTION" group="toolGroup" defaultTool="true" />
<ToggleButton id="textSelectButton" accelerator="V" icon="text-select-tool.svg, 24" tooltipText="toolbar.text.select.tooltip" tool="TEXT_SELECTION" group="toolGroup" defaultTool="true" />
<ToggleButton id="lineButton" accelerator="I" icon="line-tool.svg, 24" tooltipText="toolbar.line.tooltip" tool="LINE" group="toolGroup" />
<ToggleButton id="arrowButton" accelerator="W" icon="arrow-tool.svg, 24" tooltipText="toolbar.arrow.tooltip" tool="ARROW" group="toolGroup" />
<ToggleButton id="rectangleButton" accelerator="R" icon="rectangle-tool.svg, 24" tooltipText="toolbar.rectangle.tooltip" tool="RECTANGLE" group="toolGroup" visible="false" />

View file

@ -27,6 +27,9 @@ toolbar.clone.tooltip = Kopier-Werkzeug ausw\u00E4hlen
toolbar.erase.tooltip = Radiergummi ausw\u00E4hlen
toolbar.latex.tooltip = LaTeX-Werkzeug ausw\u00E4hlen
toolbar.text.tooltip = Textwerkzeug ausw\u00E4hlen
toolbar.bookmark.prev.tooltip = Zu vorherigem Lesezeichen springen
toolbar.bookmark.next.tooltip = Zu nächstem Lesezeichen springen
toolbar.bookmark.add.tooltip = Lesezeichen hinzufügen
toolbar.text.select.tooltip = Textauswahlwerkzeug ausw\u00E4hlen
toolbar.clear.tooltip = L\u00F6sche alle Texte und Linien
toolbar.extend.tooltip = Zeichenbereich erweitern

View file

@ -27,6 +27,9 @@ toolbar.clone.tooltip = Select copy tool
toolbar.erase.tooltip = Select eraser
toolbar.latex.tooltip = Select LaTeX tool
toolbar.text.tooltip = Select text tool
toolbar.bookmark.prev.tooltip = Jump to previous bookmark
toolbar.bookmark.next.tooltip = Jump to next bookmark
toolbar.bookmark.add.tooltip = Create bookmark
toolbar.text.select.tooltip = Select text selection tool
toolbar.clear.tooltip = Delete all annotations
toolbar.extend.tooltip = Extend drawing area

View file

@ -46,6 +46,7 @@ public abstract class SwingApplication extends ApplicationBase implements Graphi
private JFrame window;
private MainPresenter<?> mainPresenter;
@Override
protected void initInternal(String[] args) throws ExecutableException {
@ -134,10 +135,7 @@ public abstract class SwingApplication extends ApplicationBase implements Graphi
throw new ExecutableException(e);
}
if (!OPEN_FILES.isEmpty()) {
mainPresenter.openFile(OPEN_FILES.get(0));
OPEN_FILES.clear();
}
this.mainPresenter = mainPresenter;
}
@Override
@ -164,6 +162,10 @@ public abstract class SwingApplication extends ApplicationBase implements Graphi
catch (Exception e) {
throw new ExecutableException(e);
}
if (!OPEN_FILES.isEmpty()) {
mainPresenter.openFile(OPEN_FILES.get(0));
OPEN_FILES.clear();
}
}
@Override

View file

@ -1,6 +1,7 @@
package org.lecturestudio.swing.components;
import com.formdev.flatlaf.util.UIScale;
import org.lecturestudio.core.app.view.Screens;
import org.lecturestudio.core.view.Screen;
import org.lecturestudio.swing.AwtResourceLoader;
@ -20,15 +21,25 @@ import java.util.function.Consumer;
import static java.util.Objects.nonNull;
public class ExternalFrame extends JFrame {
private String name;
private Consumer<ExternalWindowPosition> positionChangedAction;
private Runnable closedAction;
private Consumer<Dimension> sizeChangedAction;
private String placeholderText;
private final Component body;
private final Component placeholder;
private boolean showBody = false;
public ExternalFrame(String name, Component body, Dimension minimumSize,
Consumer<ExternalWindowPosition> positionChangedAction, Runnable closedAction,
Consumer<Dimension> sizeChangedAction, String placeholderText) {
public ExternalFrame(String name, Component body, String placeholderText) {
super(nonNull(name) ? name : "");
this.body = body;
@ -42,26 +53,21 @@ public class ExternalFrame extends JFrame {
setIconImages(icons);
setMinimumSize(UIScale.scale(minimumSize));
if (nonNull(closedAction)) {
addClosingListener(closedAction);
}
if (nonNull(positionChangedAction) || nonNull(sizeChangedAction)) {
addMovedResizedListener(positionChangedAction, sizeChangedAction);
}
addPlaceholder();
}
@Override
public void setMinimumSize(Dimension minimumSize) {
super.setMinimumSize(UIScale.scale(minimumSize));
}
@Override
public void setVisible(boolean visible) {
if (visible && showBody) {
removePlaceholder();
add(body);
} else {
}
else {
remove(body);
addPlaceholder();
}
@ -73,6 +79,51 @@ public class ExternalFrame extends JFrame {
}
}
public void setClosedAction(Runnable closedAction) {
this.closedAction = closedAction;
if (nonNull(closedAction)) {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (nonNull(closedAction)) {
closedAction.run();
}
}
});
}
}
public void setSizeChangedAction(Consumer<Dimension> sizeAction) {
sizeChangedAction = sizeAction;
if (nonNull(sizeChangedAction)) {
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (nonNull(sizeChangedAction)) {
sizeChangedAction.accept(e.getComponent().getSize());
}
}
});
}
}
public void setPositionChangedAction(Consumer<ExternalWindowPosition> positionAction) {
positionChangedAction = positionAction;
if (nonNull(positionChangedAction) ) {
addComponentListener(new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
if (nonNull(positionChangedAction)) {
externalComponentOpenedOrMoved(e, positionChangedAction);
}
}
});
}
}
public void showBody() {
if (showBody) {
return;
@ -86,7 +137,6 @@ public class ExternalFrame extends JFrame {
repaint();
}
showBody = true;
}
@ -106,10 +156,6 @@ public class ExternalFrame extends JFrame {
showBody = false;
}
public boolean isShowBody() {
return showBody;
}
private void addPlaceholder() {
if (nonNull(placeholder)) {
add(placeholder);
@ -132,96 +178,10 @@ public class ExternalFrame extends JFrame {
}
}
private void addClosingListener(Runnable closedAction) {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (nonNull(closedAction)) {
closedAction.run();
}
}
});
}
private void addMovedResizedListener(Consumer<ExternalWindowPosition> positionChangedAction,
Consumer<Dimension> sizeChangedAction) {
addComponentListener(new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
if (nonNull(positionChangedAction)) {
externalComponentOpenedOrMoved(e, positionChangedAction);
}
}
@Override
public void componentResized(ComponentEvent e) {
if (nonNull(sizeChangedAction)) {
sizeChangedAction.accept(e.getComponent().getSize());
}
}
});
}
private void externalComponentOpenedOrMoved(ComponentEvent e, Consumer<ExternalWindowPosition> action) {
final Point position = e.getComponent().getLocationOnScreen();
final Screen screen = Screens.createScreen(e.getComponent().getGraphicsConfiguration().getDevice());
action.accept(new ExternalWindowPosition(screen, position));
}
public static class Builder {
private String name;
private Component body;
private Dimension minimumSize = new Dimension(300, 300);
private Consumer<ExternalWindowPosition> positionChangedAction;
private Runnable closedAction;
private Consumer<Dimension> sizeChangedAction;
private String placeholderText;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setBody(Component body) {
this.body = body;
return this;
}
public Builder setMinimumSize(Dimension minimumSize) {
this.minimumSize = minimumSize;
return this;
}
public Builder setPositionChangedAction(Consumer<ExternalWindowPosition> positionChangedAction) {
this.positionChangedAction = positionChangedAction;
return this;
}
public Builder setClosedAction(Runnable closedAction) {
this.closedAction = closedAction;
return this;
}
public Builder setSizeChangedAction(Consumer<Dimension> sizeChangedAction) {
this.sizeChangedAction = sizeChangedAction;
return this;
}
public Builder setPlaceholderText(String placeholderText) {
this.placeholderText = placeholderText;
return this;
}
public ExternalFrame build() {
return new ExternalFrame(name, body, minimumSize, positionChangedAction, closedAction, sizeChangedAction,
placeholderText);
}
}
}

View file

@ -155,6 +155,8 @@ public class NotificationPane extends GlassPane {
}
private void initialize() {
setLayout(new BorderLayout());
container = new JPanel(new GridBagLayout());
container.setBorder(new EmptyBorder(20, 20, 20, 20));

View file

@ -32,6 +32,8 @@ import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@ -74,6 +76,8 @@ public class SlideView extends JComponent implements org.lecturestudio.core.view
private Page page;
private final PropertyChangeSupport changes = new PropertyChangeSupport(this);
private final Rectangle canvasBounds = new Rectangle();
private AffineTransform pageTransform = new AffineTransform();
@ -114,6 +118,14 @@ public class SlideView extends JComponent implements org.lecturestudio.core.view
renderPage();
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changes.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changes.removePropertyChangeListener(propertyName, listener);
}
public final void setAlignment(Position position) {
if (alignment != position) {
alignment = position;
@ -362,6 +374,8 @@ public class SlideView extends JComponent implements org.lecturestudio.core.view
imageRect.setSize(size.getWidth(), size.getHeight());
updateViewTransform();
changes.firePropertyChange("CanvasBounds", null, canvasBounds);
}
private void initialize() {

View file

@ -455,6 +455,9 @@ public class ThumbPanel extends JPanel {
if (selected) {
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, getWidth(), getHeight());
}else if(page.isOverlay()){
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, getWidth(), getHeight());
}
else {
g2d.setColor(getBackground());

View file

@ -6,6 +6,7 @@ public enum AdaptiveTabType {
MESSAGE,
SLIDE,
PARTICIPANTS,
NOTES
NOTES,
SLIDE_NOTES
}

View file

@ -31,6 +31,7 @@ import org.lecturestudio.core.ExecutableBase;
import org.lecturestudio.core.ExecutableException;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.model.Document;
import org.lecturestudio.core.model.NotesPosition;
import org.lecturestudio.core.model.Page;
import org.lecturestudio.core.model.shape.Shape;
import org.lecturestudio.core.pdf.PdfDocument;
@ -221,6 +222,7 @@ public class PdfDocumentRenderer extends ExecutableBase {
private void createPage(PresentationParameter param,
Document newDocument, Page page) throws Exception {
Rectangle2D pageRect = param.getPageRect();
NotesPosition notesPosition = page.getDocument().getSplitSlideNotesPosition();
Page newPage = newDocument.createPage(page, pageScale ? pageRect : null);
int pageIndex = newPage.getPageNumber();
@ -237,11 +239,11 @@ public class PdfDocumentRenderer extends ExecutableBase {
if (editable) {
// Tag graphics stream to be able to find it later.
graphics = (PDFGraphics2D) pdfDocument.createAppendablePageGraphics2D(
pageIndex, PdfDocument.EMBEDDED_SHAPES_KEY);
pageIndex, PdfDocument.EMBEDDED_SHAPES_KEY, NotesPosition.UNKNOWN);
}
else {
graphics = (PDFGraphics2D) pdfDocument.createAppendablePageGraphics2D(
pageIndex);
pageIndex, NotesPosition.UNKNOWN);
}
SwingGraphicsContext gc = new SwingGraphicsContext(graphics);
@ -256,15 +258,18 @@ public class PdfDocumentRenderer extends ExecutableBase {
}
AffineTransform annotTransform = transform.createInverse();
Rectangle2D mediaBox = pdfDocument.getPageMediaBox(pageIndex);
Rectangle2D mediaBox = pdfDocument.getPageMediaBox(pageIndex, notesPosition);
double pageWidth = mediaBox.getWidth();
double sx = pageWidth * annotTransform.getScaleX();
double tx = pageWidth * annotTransform.getTranslateX();
double ty = pageWidth * annotTransform.getTranslateY();
// Move to top-left corner.
gc.translate(-tx, ty + mediaBox.getHeight());
if(page.getDocument().getActualSplitSlideNotesPosition() == NotesPosition.LEFT) {
mediaBox.setRect(mediaBox.getWidth(), mediaBox.getY(), mediaBox.getWidth(), mediaBox.getHeight());
tx -= pageWidth;
}
gc.translate(-tx , ty + mediaBox.getHeight());
gc.scale(sx, -sx);
// Draw shapes.

View file

@ -73,6 +73,7 @@ public class ViewLoader<T extends Container> extends SwingEngine<T> {
tagLibrary.registerTag("DisplayPanel", DisplayPanel.class);
tagLibrary.registerTag("DocumentPreview", DocumentPreview.class);
tagLibrary.registerTag("EllipseToolPreview", EllipseToolPreview.class);
tagLibrary.registerTag("FormattedTextField", JFormattedTextField.class);
tagLibrary.registerTag("IPTextField", IPTextField.class);
tagLibrary.registerTag("LevelMeter", LevelMeter.class);
tagLibrary.registerTag("LineToolPreview", LineToolPreview.class);

View file

@ -61,7 +61,9 @@ public class NotificationPopup extends JWindow {
private final static String CONFIG_FILE = "resources/notification.properties";
private final Object lock = new Object();
private final NotificationManager manager;
private final LinearGradientPaint gradient;
private final JTextArea messageLabel;
@ -80,9 +82,7 @@ public class NotificationPopup extends JWindow {
private boolean pending;
private NotificationManager manager;
/**
* Create a new {@code NotificationPopup}.
*
@ -161,6 +161,8 @@ public class NotificationPopup extends JWindow {
height += 30;
}
System.out.println(width + " " + height);
setAlwaysOnTop(true);
setSize(width, height);

View file

@ -55,6 +55,8 @@ import org.lecturestudio.swing.util.SwingUtils;
*/
public class SwingNotificationPopupManager implements NotificationPopupManager {
private static final Dimension OFFSET = new Dimension(0, 55);
/** Visible notifications. */
private final Map<Position, List<JWindow>> popupMap = new HashMap<>();
@ -102,7 +104,7 @@ public class SwingNotificationPopupManager implements NotificationPopupManager {
return;
}
popup.setLocation((int) location.getX(), (int) location.getY());
popup.setLocation((int) location.getX() + OFFSET.width, (int) location.getY() + OFFSET.height);
popup.setVisible(true);
addPopup(popup, position);
@ -171,8 +173,7 @@ public class SwingNotificationPopupManager implements NotificationPopupManager {
private Point2D getLocation(Rectangle contentBounds, Rectangle rootBounds,
Position position) {
Point2D location = getInitialLocation(contentBounds, rootBounds,
position);
Point2D location = getInitialLocation(contentBounds, rootBounds, position);
List<JWindow> popups = popupMap.get(position);
if (isNull(popups)) {