This website uses cookies for visitor traffic analysis. By using the website, you agree with storing the cookies on your computer.More information

Layout Managers

General questions regarding the development with JavaFX UI for JVx.

Layout Managers

Postby javalearner » Thu Apr 12, 2018 2:16 am

Hi,
I noticed there are no window layout managers currently implemented (or I am blind and not finding them)

So I went ahead and made one for my own application. It appears to work really well in my case.

Wondering if this would be worthwhile incorporating into the core window manager?

(PS: some tidy up can be done, like changing the Boolean "VerHor" into an Enum and making the cascade offset settable, also, if this is internal, some of the checks and parameters, ie desktop, would not be needed and so on)

Hope this is useful.

Cheers.

Code: Select all
/*
 * Copyright 2018 Wattsie.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import com.sibvisions.rad.ui.javafx.ext.mdi.FXDesktopPane;
import com.sibvisions.rad.ui.javafx.ext.mdi.FXInternalWindow;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;

/**
 *
 */
public class FXInternalWindowLayout {

    private static final Double CASCADE_PERCENT_WIDTH = 0.75;
    private static final Double CASCADE_PERCENT_HEIGHT = 0.7;
    private static final int CASCADE_OFFSET_X = 32;
    private static final int CASCADE_OFFSET_Y = 32;

    /**
     * Rearrange windows in cascaded layout
     *
     * @param desktop
     * @param IncludeMinimized
     */
    public static void ArrangeFXIntWinInCascadeLayout(FXDesktopPane desktop, Boolean IncludeMinimized) {
        ObservableList<FXInternalWindow> winndowsToBeResized = FXCollections.observableArrayList();
        // Get all Window Nodes from our desktop
        ObservableList<FXInternalWindow> windowNodes = desktop.getWindowManager().getWindows();
        // Loop over all nodes
        for (FXInternalWindow curWindow : windowNodes) {
            // Reset Maximized windows to Normal State
            if (curWindow.getState().equals(FXInternalWindow.State.MAXIMIZED)) {
                curWindow.setState(FXInternalWindow.State.NORMAL);
            }
            // If needed, set Minimized windows to Normal State
            if ((curWindow).getState().equals(FXInternalWindow.State.MINIMIZED) && (IncludeMinimized)) {
                (curWindow).setState(FXInternalWindow.State.NORMAL);
            }
            // Add this window to the "to be resized" list
            winndowsToBeResized.add(curWindow);
        }
        // Rearrange windows if we have a DesktopPane and 1 or more Windows
        if (windowNodes.size() > 0) {
            int loop = 0;
            int newWindowWidth;
            int newWindowHeight;
            for (FXInternalWindow windowToResize : winndowsToBeResized) {
                newWindowWidth = (int) (desktop.getWidth() * CASCADE_PERCENT_WIDTH);
                newWindowHeight = (int) (desktop.getHeight() * CASCADE_PERCENT_HEIGHT);
                if ((((loop * CASCADE_OFFSET_X) + newWindowWidth) > ((int) desktop.getWidth())) || ((((loop - 1) * CASCADE_OFFSET_Y) + newWindowHeight) > ((int) desktop.getHeight()))) {
                    loop = 0;
                }
                windowToResize.resizeRelocate((loop * CASCADE_OFFSET_X), (loop * CASCADE_OFFSET_Y), newWindowWidth, newWindowHeight);
                loop++;
            }
        }
        // Set last active window as active
        desktop.getWindowManager().windowsProperty().get(desktop.getWindowManager().windowsProperty().getSize() - 1).setActive(true);
    }

    /**
     * Rearrange all FXInternalWindow Windows.
     * <p>
     * Rearranged windows in either Horizontal Tile, Vertical Tile, or both Horizonal and Vertical layouts.<br>
     * <p>
     * @param desktop FXDesktopPane pane to be organized
     * @param VerHor True for Vertical arrangement, False for Horizontal arrangement
     * @param IncludeMinimized Include Minimized windows. Normally only Maximized and Normal State windows are resized.
     */
    public static void ArrangeFXIntWinInTileLayout(FXDesktopPane desktop, Boolean VerHor, Boolean IncludeMinimized) {
        ObservableList<FXInternalWindow> winndowsToBeResized = FXCollections.observableArrayList();
        // Get all Window Nodes from our desktop
        ObservableList<Node> windowNodes = desktop.getWindowManager().getPane().getChildren();
        // Loop over all nodes
        for (Object object : windowNodes) {
            // Verify we have a valid FXInternalWindow object
            if (object.getClass().equals(FXInternalWindow.class)) {
                // Reset Maximized windows to Normal State
                if (((FXInternalWindow) object).getState().equals(FXInternalWindow.State.MAXIMIZED)) {
                    ((FXInternalWindow) object).setState(FXInternalWindow.State.NORMAL);
                }
                // If needed, set Minimized windows to Normal State
                if (((FXInternalWindow) object).getState().equals(FXInternalWindow.State.MINIMIZED) && (IncludeMinimized)) {
                    ((FXInternalWindow) object).setState(FXInternalWindow.State.NORMAL);
                }
                // Add this window to the "to be resized" list
                winndowsToBeResized.add(((FXInternalWindow) object));
            }
        }
        // Rearrange windows if we have a DesktopPane and 1 or more Windows
        if (windowNodes.size() > 0) {
            int[] layoutArray = FXInternalWindowLayout.getBestWindowLayoutArray(winndowsToBeResized.size());
            int numCols = 0;
            int numRows = 0;
            int numRowsLastCol = 0;
            int newWindowWidth = 0;
            int newWindowHeight = 0;
            int newWindowHeightLastCol = 0;
            // Single option layout
            if ((layoutArray.length == 2)) {
                numCols = layoutArray[0];
                numRows = layoutArray[1];
                newWindowWidth = (int) desktop.getWidth() / numCols;
                newWindowHeight = (int) desktop.getHeight() / numRows;
                int currentWindowIndex = 0;
                for (int colIndex = 0; colIndex < numCols; colIndex++) {
                    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
                        winndowsToBeResized.get(currentWindowIndex).resizeRelocate((colIndex * newWindowWidth), (rowIndex * newWindowHeight), newWindowWidth, newWindowHeight);
                        currentWindowIndex++;
                    }
                }
            }
            // Single option layout with extra column
            if ((layoutArray.length == 4)) {
                numCols = layoutArray[0] + layoutArray[2];
                numRows = layoutArray[1];
                numRowsLastCol = layoutArray[3];
                newWindowWidth = (int) desktop.getWidth() / numCols;
                newWindowHeight = (int) desktop.getHeight() / numRows;
                newWindowHeightLastCol = (int) desktop.getHeight() / numRowsLastCol;
                int currentWindowIndex = 0;
                for (int colIndex = 0; colIndex < numCols - 1; colIndex++) {
                    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
                        winndowsToBeResized.get(currentWindowIndex).resizeRelocate((colIndex * newWindowWidth), (rowIndex * newWindowHeight), newWindowWidth, newWindowHeight);
                        currentWindowIndex++;
                    }
                }
                for (int j = 0; j < numRowsLastCol; j++) {
                    winndowsToBeResized.get(currentWindowIndex).resizeRelocate(((numCols - 1) * newWindowWidth), (j * newWindowHeightLastCol), newWindowWidth, newWindowHeightLastCol);
                    currentWindowIndex++;
                }
            }
            // Dual option
            if ((layoutArray.length == 6)) {
                if (VerHor) {
                    numCols = layoutArray[0];
                    numRows = layoutArray[1];
                } else {
                    numCols = layoutArray[4];
                    numRows = layoutArray[5];
                }
                newWindowWidth = (int) desktop.getWidth() / numCols;
                newWindowHeight = (int) desktop.getHeight() / numRows;
                int currentWindowIndex = 0;
                for (int colIndex = 0; colIndex < numCols; colIndex++) {
                    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
                        winndowsToBeResized.get(currentWindowIndex).resizeRelocate((colIndex * newWindowWidth), (rowIndex * newWindowHeight), newWindowWidth, newWindowHeight);
                        currentWindowIndex++;
                    }
                }
            }
            // Dual option with extra column
            if ((layoutArray.length == 8)) {
                if (VerHor) {
                    numCols = layoutArray[0] + layoutArray[2];
                    numRows = layoutArray[1];
                    numRowsLastCol = layoutArray[3];
                } else {
                    numCols = layoutArray[4] + layoutArray[6];
                    numRows = layoutArray[5];
                    numRowsLastCol = layoutArray[7];
                }
                newWindowWidth = (int) desktop.getWidth() / numCols;
                newWindowHeight = (int) desktop.getHeight() / numRows;
                newWindowHeightLastCol = (int) desktop.getHeight() / numRowsLastCol;
                int currentWindowIndex = 0;
                for (int colIndex = 0; colIndex < numCols - 1; colIndex++) {
                    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
                        winndowsToBeResized.get(currentWindowIndex).resizeRelocate((colIndex * newWindowWidth), (rowIndex * newWindowHeight), newWindowWidth, newWindowHeight);
                        currentWindowIndex++;
                    }
                }
                for (int j = 0; j < numRowsLastCol; j++) {
                    winndowsToBeResized.get(currentWindowIndex).resizeRelocate(((numCols - 1) * newWindowWidth), (j * newWindowHeightLastCol), newWindowWidth, newWindowHeightLastCol);
                    currentWindowIndex++;
                }
            }
        }
        // Set last active window as active
        desktop.getWindowManager().windowsProperty().get(desktop.getWindowManager().windowsProperty().getSize() - 1).setActive(true);
    }

    /**
     * Calculate the best window layout for the number of windows provided.
     *
     * @param numWin Number of Windows
     * @return Either int[2], int[6] or int[8] of columns and rows
     */
    public static int[] getBestWindowLayoutArray(int numWin) {
        Double sqrtNumWin = Math.sqrt(numWin);
        int floorSqrtNumWin = (int) Math.floor(sqrtNumWin);
        Double modSqrtNumWinFloorNumWin = sqrtNumWin % floorSqrtNumWin;
        Double modNumWinFloorNumWin = numWin % (double) floorSqrtNumWin;
        Double numWinDivfloorNumWin = numWin / (double) floorSqrtNumWin;
        int x1, y1, x2, y2, x3, y3, x4, y4;
        x2 = y2 = x3 = y3 = x4 = y4 = -1;
        if (modSqrtNumWinFloorNumWin == 0) {
            x1 = sqrtNumWin.intValue();
            y1 = floorSqrtNumWin;
        } else {
            if (modNumWinFloorNumWin == 0) {
                x1 = numWinDivfloorNumWin.intValue();
                y1 = floorSqrtNumWin;
                x2 = -1;
                y2 = -1;
                x3 = floorSqrtNumWin;
                y3 = numWinDivfloorNumWin.intValue();
            } else {
                if (modSqrtNumWinFloorNumWin < 0.5) {
                    x1 = floorSqrtNumWin - 1;
                    y1 = floorSqrtNumWin;
                    x2 = 1;
                    y2 = (numWin - ((floorSqrtNumWin - 1) * (floorSqrtNumWin)));
                } else {
                    x1 = floorSqrtNumWin;
                    y1 = floorSqrtNumWin;
                    x2 = 1;
                    y2 = (numWin - (floorSqrtNumWin * floorSqrtNumWin));
                    x3 = (floorSqrtNumWin - 1);
                    y3 = (floorSqrtNumWin + 1);
                    x4 = 1;
                    y4 = (numWin - ((floorSqrtNumWin - 1) * (floorSqrtNumWin + 1)));
                }
            }
        }
        if (x4 != -1) {
            int[] A = {x1, y1, x2, y2, x3, y3, x4, y4};
            return (A);
        } else if (x3 != -1) {
            int[] A = {x1, y1, x2, y2, x3, y3};
            return (A);
        } else if (x2 != -1) {
            int[] A = {x1, y1, x2, y2};
            return (A);
        } else {
            int[] A = {x1, y1};
            return (A);
        }
    }

}


Cheers.
Last edited by javalearner on Sun Apr 15, 2018 1:21 pm, edited 1 time in total.
javalearner
 
Posts: 6
Joined: Wed Mar 28, 2018 10:11 am

Re: Layout Managers

Postby rzenz » Thu Apr 12, 2018 9:25 am

You're blind or I misunderstood what you're trying to do, you're looking for the com.sibvisions.rad.ui.javafx.ext.mdi.IFXWindowManager interface and the com.sibvisions.rad.ui.javafx.ext.mdi.windowmanagers package, with the later containing all our implementations.

I'm a little bit tight on time right now, but I've been reading through your code and all in all, it seems quite solid and works well. I'd love to include something like this in our JFXtensions library.

I'll do a rather full code review further down, but given some thought, these two should most likely not reside inside the same window manager. The tiling should definitely be a window manager (based on our interface/abstract class), but the cascading one should not be in there. I'm not sure whether it makes sense to have that as a separate window manager, as we already have a floating one, but our one cannot tile the windows in such a way (which is neat indeed). You can have a look at the com.sibvisions.rad.ui.javafx.ext.mdi.windowmanagers.FXDesktopWindowManager (not the best name, in hindsight, FXFloatingWindowManager would have been better) and see if it might fit there, somehow. I'm not sure how, though. Maybe we could add an additional method to interface which allows to "restore the default layout", or something like that. When invoked, the FXDesktopWindowsManager would cascade the windows with your algorithm. But I'm not very fond of that idea, as it is pretty much the only window manager that needs that...needs some thinking.

Okay, here goes a quick review of the code. First, Javadoc is missing. You're right, you should use Enums when a multi-state value is needed, a boolean is only appropriate when the two values are true and false. Also, I'm not a fan of unnecessarily shorting variable names (numWin f.e.).

Code: Select all
12: private static final Double CASCADE_PERCENT_WIDTH = 0.75;
13: private static final Double CASCADE_PERCENT_HEIGHT = 0.7;
14: private static final int CASCADE_OFFSET_X = 32;
15: private static final int CASCADE_OFFSET_Y = 32;


These should most likely be properties of the window manager. Also, why the Double instead of double?

There is a quite an important difference in Java between the primitives (boolean, int, float, double, ...) and the accompanying classes (Boolean, Integer, Float, Double, ...). Stick to the primitives.

Code: Select all
23: public static void ArrangeFXIntWinInCascadeLayout(FXDesktopPane desktop, Boolean IncludeMinimized) {


The Java naming convention are as follows:

  • Package: lowercase with underscores if absolutely needed.
  • Class: UpperCamelCase
  • Functions: lowerCamelCase
  • Constants/Enum Values: UPPER_SNAKE_CASE
  • Members/Variables/Parameters/Arguments: lowerCamelCase

Code: Select all
48: if ((((loop * CASCADE_OFFSET_X) + newWindowWidth) > ((int) desktop.getWidth())) || ((((loop - 1) * CASCADE_OFFSET_Y) + newWindowHeight) > ((int) desktop.getHeight()))) {
49:     loop = 0;
50: }


You should extract these parts into single variables to make the if easier to read, currently it looks like a LISP expression.

Code: Select all
51: windowToResize.resizeRelocate((loop * CASCADE_OFFSET_X), (loop * CASCADE_OFFSET_Y), newWindowWidth, newWindowHeight);


I'm quite a fan of breaking long lines, especially to make the argument list of functions more readable, like this:

Code: Select all
51: windowToResize.resizeRelocate(
??:         (loop * CASCADE_OFFSET_X),
??:         (loop * CASCADE_OFFSET_Y),
??:         newWindowWidth,
??:         newWindowHeight);


But that is personal preference, I'm afraid.

Code: Select all
 68: public static void ArrangeFXIntWinInTileLayout(FXDesktopPane desktop, Boolean VerHor, Boolean IncludeMinimized) {
SNIP
179: }


Okay, this function is quite big and does a lot of things, it should be split up. For example, the first part about resetting the windows is reused, so it should be its own function which can be called from both methods. Also the actual layout parts seem to be independent of each other, so they could go into their own functions to make the main function more readable.

Code: Select all
187: public static int[] getBestWindowLayoutArray(int numWin) {


I'm not a fan of using arrays for such things, a dedicated domain class would be much better. Doesn't JavaFX already have a class which holds multiple points?

Code: Select all
193: int x1, y1, x2, y2, x3, y3, x4, y4;
194: x2 = y2 = x3 = y3 = x4 = y4 = -1;


Neither am I a fan of this, though, this declaration would go away with a dedicated domain class.
User avatar
rzenz
 
Posts: 36
Joined: Mon Dec 12, 2016 1:40 pm
Location: Vienna, Austria

Re: Layout Managers

Postby Development@SIB » Thu Apr 12, 2018 9:43 am

Please stay friendly!

Here are our rules for code contributions: https://doc.sibvisions.com/jvx/join/help

We aren't allowed to use posted code without a license definition or CLA!
User avatar
Development@SIB
 
Posts: 325
Joined: Mon Sep 28, 2009 1:54 pm

Re: Layout Managers

Postby javalearner » Fri Apr 13, 2018 11:48 am

Thank you, I consider myself completely and extensible chastised.

First thing, any code I post is opensource and free to use however you want. If I have the time to clean this up, and you are happy to consider including, I will look into your CLA document etc...

I had reviewed, and could not see any type of window layout management, to auto-magically arranging and cleaning up all windows.

As mentioned, Tiling and Cascading windows would only really make sense within the "FXDesktopWindowManager", as they are used to rearrange the layout of the windows, thus don't make sense within a Single or a Tabbed window manager. However, you may want to rearrange Tabs alphabetically... Not sure.

Ill will take another look when time permits.

Also, Ill be sure to modify any code to follow your coding styles so I dont get in trouble again :-).

Cheers.
javalearner
 
Posts: 6
Joined: Wed Mar 28, 2018 10:11 am

Re: Layout Managers

Postby Support@SIB » Fri Apr 13, 2018 12:09 pm

I guess the previous comment was meant for rzenz :)

Please add the OpenSource license to your code and it'll be fine.

OpenSource isn't clear enough because GPL is a problem for JVx and MIT, BSD or Apache are fine ;)
User avatar
Support@SIB
 
Posts: 353
Joined: Mon Sep 28, 2009 1:56 pm

Re: Layout Managers

Postby rzenz » Fri Apr 13, 2018 12:28 pm

javalearner wrote:I had reviewed, and could not see any type of window layout management, to auto-magically arranging and cleaning up all windows.


Because there isn't, at least not as far as I know, but I'm open to suggestions as to where to add it. Maybe adding something like FXDesktopPane.rearrangeWindows()/layoutWindows(), which is forwarding to IFXWindowManager.arrangeWindows()/performDefaultLayout() or some such, would work well. Though, there might be multiple default layouts offered, but maybe calling the (correctly casted) window manager directly would be a good choice in that case.

javalearner wrote:However, you may want to rearrange Tabs alphabetically... Not sure.


That is actually a quite sane assumption, I like it.

javalearner wrote:Also, Ill be sure to modify any code to follow your coding styles so I dont get in trouble again :-).


There's no trouble here, don't worry, if there would be a serious problem with you or your code, you wouldn't get a code review (or response, for that matter). Also don't worry about the formatting of the code, there is an automatic chain for the formatting set up which is just one button press away (I just realized that that is missing from the project, my fault, sorry, I will add it when time allows).

When it comes to how the code is written, I'll happily comment on the code and do reviews to get that sorted out. But overall, it already looks quite solid and I'm all for it.
User avatar
rzenz
 
Posts: 36
Joined: Mon Dec 12, 2016 1:40 pm
Location: Vienna, Austria


Return to Development