Bläddra i källkod

Several changes:

[CLI] Team edit function.
[CLI] Conditional compilation for win32.
[CLI] Fixed an error in the update command.
[GUI] All panelr organized with sizers. Now the UI is better looking and adapts to different window sizes.
[DOC] Updated README. Formatting, images, and building instructions.
Iñigo Valentin 4 år sedan
förälder
incheckning
d030253815

+ 57 - 27
README.md

@@ -3,17 +3,32 @@ RuneOptimizer
 
 # A rune optimizer for Summoners War players #
 
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-cli-1.png"/>
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-cli-2.png"/>
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-1.png"/>
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-2.png"/>
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-3.png"/>
-<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-4.png"/>
+RuneOptimizer is a tool for Summoners War players. It helps players get the
+most of their monstesrs and units by calculating rune combinations matching
+user-definedcriteria.
+
+It is indeed inspired by Xzandro's
+[Summoners War Optimizer](https://tool.swop.one), wich is an amazing tool.
+
+It's not a webapp, but a locally installable application, to get the most out
+of your CPU. Includes a fully functional command line utility, and a graphical
+interface for ease-of use.
+
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-cli-1.png" width=170/>
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-cli-2.png" width=170/>
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-1.png" width=170/>
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-2.png" width=170/>
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-3.png" width=170/>
+<img src="https://runeoptimizer.inigovalentin.com/img/cover/runeoptimizer-gui-4.png" width=170/>
 
 ## Features ##
 
-* Blazingly fast
-* Low memory consumption
+* **Fast:** With multiple threads, it will have the best rune combination for your monster in no time. Roughly speaking, it can calculate a few billion combinations in under a minute on a run-of-the-mill computer.
+* **Customizable:** Many options can be selected for optimizations: stats for even slots, required and acceptable rune-sets, using runes from storage, or excluding certain teams or units...
+* **Easy:** Just pick a monster. Click 'Optimize'. Wait for the results! While advanced options can also be set, the default ones are an easy way to find out if you can give a quick boost to your monsters.
+* **Safe:** No funny moves. RuneOptimizer won't need your login details, and all your data is safely stored locally, and never sent anywhere. In fact, it doesn't eve use the network, all calculations are done locally.
+* **Efficient:** It only takes a few megabytes of RAM to run the optimizer.
+* **Open Source:** The source code of RuneOptimizer is licensed under a GPL license, and it's publicly available for anyone to see or improve as they see fit.
 
 ## How to use ##
 
@@ -30,65 +45,80 @@ RuneOptimizer update path-to-your-file.json --six-stars --with-runes
 
 The parameters `--six-stars` and `--with-runes` are optional, and they are used to import only monsters at level 40 or with runes equiped.
 
+### Using the GUI ###
+
+The GUI is pretty self explanatory, but the first step is the same. From the `Update` menu, select the `Update from JSON` option to load your data.
+
+Once it's done, you can browse your units and teams, and use the optimizer form.
 
 ## Build
 
 ### Build on GNU/Linux ###
 
+Install dependencies.
+
 ```console
 apt install gcc libsqlite3-dev libjson-c-dev
 ```
 
-Just `make`
+For the GUI, you will also need the following dependencies:
+
+```console
+apt install python3 python3-wxgtk4.0 python3-minidb python3-appdirs python3-sh
+```
+
+Just run `make` and `sudo make install`
 
 ### Build on Windows ###
 
 Well, this was painfull to get right
 
 1. Install MinGW for the compiler.
-    You can follow this great tutorial: https://dev.to/gamegods3/how-to-install-gcc-in-windows-10-the-easier-way-422j
+    You can follow this [great tutorial](https://dev.to/gamegods3/how-to-install-gcc-in-windows-10-the-easier-way-422j).
+
+    Once it's complete and the installer runes, make sure to mark `pthread` for
+    instalation.
 
 
 2. Get the sqlite3 libraries.
-    (Credit for this part and many thanks to http://source.online.free.fr/Windows_HowToCompileSQLite)
-    Download https://www.sqlite.org/2022/sqlite-amalgamation-3370200.zip . It contains four files:
 
+    (Credit for this part and many thanks to [this tutorial](http://source.online.free.fr/Windows_HowToCompileSQLite))
+
+    Download [sqlite-amalgamation-3370200.zip](https://www.sqlite.org/2022/sqlite-amalgamation-3370200.zip). It contains four files:
     * shell.c
     * sqlite3.c
     * sqlite3.h
     * sqlite3ext.h
 
-    They need to be copied to the compiler lib directory `C:\MinGW\lib\`
+    They need to be copied to the compiler lib directory `C:\MinGW\lib\`.
 
-    Next, download https://www.sqlite.org/2022/sqlite-dll-win64-x64-3370200.zip (or https://www.sqlite.org/2022/sqlite-dll-win32-x86-3370200.zip for 32 bit versions of Windows). Extract the zip, and copy the file to these to directories:
+    Next, download [sqlite-dll-win64-x64-3370200.zip](https://www.sqlite.org/2022/sqlite-dll-win64-x64-3370200.zip) (or [sqlite-dll-win32-x86-3370200.zip](https://www.sqlite.org/2022/sqlite-dll-win32-x86-3370200.zip) for 32 bit versions of Windows). Extract the zip, and copy the file to these directories:
 
     * The compiler lib directory `C:\MinGW\lib\`
-    * The system lib directory `C:\Windows\SysWOW64\` (or `C:\Windows\System32\` for 32bit versions of Windows)
+    * The system lib directory `C:\Windows\SysWOW64\`
+      (or `C:\Windows\System32\` for 32bit versions of Windows)
 
 3. Build json-c.
-    For this step you need VisualStudio. You can download it from here
-    https://visualstudio.microsoft.com/downloads/
-    If VisualStudio is not installed in english by default, once it's done you will also need to install the english language pack.
-    Once i'ts installed, follow the excelent instructions in:
-    https://github.com/json-c/json-c#building-on-unix-and-windows-with-vcpkg-
-    When done, you will end up with a folder named `vcpkg\packages\json-c_x86-windows`
-    From there, you need to copy:
+    For this step you need VisualStudio. You can download it from [here](https://visualstudio.microsoft.com/downloads/). If VisualStudio is not installed in english by default, once it's done you will also need to install the english language pack.
+    Once i'ts installed, follow this excelent [tutorial](https://github.com/json-c/json-c#building-on-unix-and-windows-with-vcpkg-).
+    When done, you will end up with a folder named `vcpkg\packages\json-c_x86-windows`. From there, you need to copy:
     * The file `lib\json-c.lib` to `C:\MinGW\lib`
     * The folder `include\json-c\` to `C:\MinGW\lib\json-c\`
-    * The file `bin\json-c.dll` to `C:\Windows\SysWOW64\` (or `C:\Windows\System32\` for 32bit versions of Windows)
+    * The file `bin\json-c.dll` to `C:\Windows\SysWOW64\`
+      (or `C:\Windows\System32\` for 32bit versions of Windows)
 
 
 4. Compile.
     Open a console and go to the project directory. From there, go to `src/RuneOptimizer`
+
     To compile, use the command:
     ```console
-    gcc RuneOptimizer.c -o ../../RuneOptmizer.exe -I C:\MinGW\lib -lsqlite3 -ljson-c
+    gcc runeoptimizer.c */*.c -o ../../runeoptmizer.exe -I C:\MinGW\lib -lsqlite3 -ljson-c -lpthread
     ```
 
     Now, you'll have an executable in the project main directory.
 
 5. (Optional) Install python to use the GUI.
-    If you want to use the program GUI, download and install Python3 for Windows from here (required minimum version is 3.6):
-    Download https://bootstrap.pypa.io/get-pip.py and save in the same directory that the Python executable (in my case `C:\Python32`)
+    If you want to use the program GUI, download and install Python3 for Windows from [here](https://www.python.org/downloads/windows/) (required minimum version is 3.6):
     During instalation, make sure to check `Install PIP`
-    In a console, run `py -m pip install wxPython` to install the required packages.
+    In a console, run `py -m pip install wxPython appdirs` to install the required packages.

+ 4 - 0
src/cli/runeoptimizer/db/db.c

@@ -196,7 +196,11 @@ static void db_create_directories(){
         dir_to_make[last_path_separator] = '\0';
 
     }
+    #ifdef _WIN32
+    mkdir(dir_to_make);
+    #else
     mkdir(dir_to_make, 0777);
+    #endif
     return;
 }
 

+ 47 - 0
src/cli/runeoptimizer/error/error.h

@@ -289,6 +289,46 @@
  */
 #define ERROR_INPUT_TEAM_DELETE_ARGUMENT_COUNT -131
 
+/**
+ * Team command user input error.
+ *
+ * Indicates that an invalid number of arguments have been passed to the
+ * team edition option. can take one ow two arguments: a new team name and a
+ * new priority.
+ */
+#define ERROR_INPUT_TEAM_EDIT_ARGUMENT_COUNT -132
+
+/**
+ * Team command user input error.
+ *
+ * Indicates that an the two argument passed to the edit option are numeric and
+ * interpreted as priorities.
+ */
+#define ERROR_INPUT_TEAM_EDIT_TWO_PRIORITIES -133
+
+/**
+ * Team command user input error.
+ *
+ * Indicates that an the two argument passed to the edit option are
+ * alphanumeric and interpreted as team names.
+ */
+#define ERROR_INPUT_TEAM_EDIT_TWO_NAMES -134
+
+/**
+ * Team command user input error.
+ *
+ * Indicates that the new team name is too long or too short.
+ */
+#define ERROR_INPUT_TEAM_EDIT_NAME_LEN -135
+
+/**
+ * Team command user input error.
+ *
+ * Indicates that an invalid priority has been passed to the team edition
+ * option.
+ */
+#define ERROR_INPUT_TEAM_EDIT_PRIORITY -124
+
 /**
  * Update command user input error.
  *
@@ -449,6 +489,13 @@
  */
 #define ERROR_DB_UPDATE_UNITS -223
 
+/**
+ * Database error.
+ *
+ * Error updating the table teams.
+ */
+#define ERROR_DB_UPDATE_TEAMS -224
+
 /**
  * Database error.
  *

+ 7 - 1
src/cli/runeoptimizer/optimize/optimize.c

@@ -83,6 +83,12 @@
  */
 #define LIMIT_RUNES_PER_SLOT_STRING "500"
 
+#ifdef _WIN32
+#define MAX_THREADS 8
+#else
+#define MAX_THREADS 1
+#endif
+
 /**
  * Parses the arguments for the optimize command.
  *
@@ -1014,7 +1020,7 @@ static int optimize_parse_arguments(
             if (i < argc - 1){
                 unsigned char threads_tmp = atoi(argv[i + 1]);
                 if (threads_tmp < 1) threads_tmp = 1;
-                else if (threads_tmp > 8) threads_tmp = 8;
+                else if (threads_tmp > MAX_THREADS) threads_tmp = MAX_THREADS;
                 options->threads = threads_tmp;
                 i ++; // Advance one position in argument reading
             }

+ 18 - 3
src/cli/runeoptimizer/runeoptimizer.c

@@ -124,20 +124,35 @@
  */
 #define CMD_GUI "gui"
 
+#ifdef _WIN32
 /**
- * Home environment variable.
+ * Home environment variable for Windows.
+ *
+ * Key to get the environment variable to determine the user directory.
+ */
+#define ENV_VAR_HOME "APPDATA"
+/**
+ * The default path to the database for Windows.
+ *
+ * Relative (and to be appended to) the user directory path.
+ * @todo: This won't work on Windows.
+ */
+#define DEFAULT_DB_PATH_FROM_HOME "/RuneOptimizer/data.sqlite"
+#else
+/**
+ * Home environment variable for Linux.
  *
  * Key to get the environment variable to determine the user directory.
  */
 #define ENV_VAR_HOME "HOME"
-
 /**
- * The default path to the database.
+ * The default path to the database for Linux.
  *
  * Relative (and to be appended to) the user directory path.
  * @todo: This won't work on Windows.
  */
 #define DEFAULT_DB_PATH_FROM_HOME "/.local/share/RuneOptimizer/data.sqlite"
+#endif
 
 /**
  * Argument cutter for program arguments.

+ 3 - 0
src/cli/runeoptimizer/team/team.c

@@ -42,6 +42,9 @@ extern int team(int argc, char *argv[]){
     }
     else if (strcmp(argv[0], "delete") == 0){
         return(team_delete(argc - 1, argv + 1));
+    }
+     else if (strcmp(argv[0], "update") == 0){
+        return(team_edit(argc - 1, argv + 1));
     }
     else if (strcmp(argv[0], "add_unit") == 0){
         return(team_add_unit(argc - 1, argv + 1));

+ 31 - 0
src/cli/runeoptimizer/team/team.h

@@ -26,6 +26,16 @@
 
 #pragma once
 
+/**
+ * Smallest length for team names.
+ */
+#define TEAM_NAME_MIN_LEN 4
+
+/**
+ * Biggest length for team names.
+ */
+#define TEAM_NAME_MAX_LEN 128
+
 /**
  * Adds a unit to a team.
  *
@@ -41,6 +51,27 @@
  */
 extern int team_add_unit(int argc, char *argv[]);
 
+/**
+ * Adds a unit to a team.
+ *
+ * Edits a team info updating the table 'units_teams'. In case of error, it
+ * will print a description to stderr.
+ *
+ * @param[in] argc Number of arguments passed to the option. It should be
+ * exactly 2 or 3.
+ * @param[in] argv Aguments passed to the option. They can be 2 or 3, and the
+ * first one must be the ID of the team to edit. For the others,  numeric only
+ * argument will be recognized as a new team priority and validated as such. An
+ * alphanumeric argument will be considered a team name. Both are optional,
+ * but at least one must be present. When two arguments are supplied, each one
+ * must be of one kind.
+ * @return {@link SUCCESS} if the team was updated, or an error defined in
+ * {@link error.h} if there were errors. Note that the first parameter, the
+ * team ID, is not validated, and it will still return {@link SUCCESS} even if
+ * the ID is wrong and no actual changes were made in the database.
+ */
+extern int team_edit(int argc, char *argv[]);
+
 /**
  * Creates a team.
  *

+ 135 - 0
src/cli/runeoptimizer/team/team_edit.c

@@ -0,0 +1,135 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file team_team.c
+ *
+ * Implementation of {@link team_edit}.
+ *
+ * This file implements the function {@link team_edit} declared in
+ * {@link team.h}. It also defines and implements some static functions used by
+ * it.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "../runeoptimizer.h"
+#include "../error/error.h"
+#include "../db/db.h"
+#include "team.h"
+
+extern int team_edit(int argc, char *argv[]){
+    if (argc < 1 || argc > 3){
+        fprintf(
+          stderr, "Team edition takes 2 or 3 arguments, %d supplied\n", argc
+        );
+        return(ERROR_INPUT_TEAM_EDIT_ARGUMENT_COUNT);
+    }
+    short new_priority = -1;
+    char new_name[TEAM_NAME_MAX_LEN + 1];
+    strcpy(new_name, "");
+
+    for (int i = 1; i < argc; i ++){
+        int numeric = TRUE;
+        for (int j = 0; j < strlen(argv[i]); j ++){
+            if (0 == isdigit(argv[i][j])) numeric = FALSE;
+        }
+        if (TRUE == numeric){
+            if (new_priority != -1){
+                // Priority has been set before in a previous argument. This is
+                // an error.
+                fprintf(
+                    stderr, "Both arguments are recognized as priorities.\n"
+                );
+                return(ERROR_INPUT_TEAM_EDIT_TWO_PRIORITIES);
+            }
+            new_priority = atoi(argv[i]);
+        }
+        else{
+            // Alphanumeric argument, for name
+            if (0 != strcmp("", new_name)){
+                // Name has been set before in a previous argument. This is an
+                // error.
+                fprintf(
+                    stderr, "Both arguments are recognized as team names.\n"
+                );
+                return(ERROR_INPUT_TEAM_EDIT_TWO_NAMES);
+            }
+            if (
+              strlen(argv[i]) > TEAM_NAME_MAX_LEN
+              || strlen(argv[i]) < TEAM_NAME_MIN_LEN
+            ){
+                fprintf(
+                    stderr, "Team names length must be between %d and %d.\n",
+                    TEAM_NAME_MIN_LEN, TEAM_NAME_MAX_LEN
+                );
+                return(ERROR_INPUT_TEAM_EDIT_NAME_LEN);
+            }
+            strcpy(new_name, argv[i]);
+        }
+    }
+    // Validate priority, if set
+    if (new_priority < -1 || new_priority > 50){
+        // -1 here is valid, it indicates that it's not to be updated.
+        fprintf(stderr, "Priority must be an integer between 0 and 50\n");
+        return(ERROR_INPUT_TEAM_EDIT_PRIORITY);
+    }
+
+    char *parameters[3];
+    char query[200];
+
+    // First case, name and priority are updated
+    if (new_priority != -1 && strcmp("", new_name) != 0){
+        strcpy(query, "UPDATE teams SET name = ?, priority = ? WHERE id = ?");
+        parameters[0] = malloc(sizeof(char) * (TEAM_NAME_MAX_LEN + 1));
+        strcpy(parameters[0], new_name);
+        parameters[1] = malloc(4);
+        sprintf(parameters[1], "%d", new_priority);
+        parameters[2] = malloc(sizeof(char) * strlen(argv[0]));
+        strcpy(parameters[2], argv[0]);
+    }
+
+    // Second case, update only name.
+    else if (new_priority == -1 && strcmp("", new_name) != 0){
+        strcpy(query, "UPDATE teams SET name = ? WHERE id = ?");
+        parameters[0] = malloc(sizeof(char) * (TEAM_NAME_MAX_LEN + 1));
+        strcpy(parameters[0], new_name);
+        parameters[1] = malloc(sizeof(char) * strlen(argv[0]));
+        strcpy(parameters[1], argv[0]);
+    }
+
+    // Thirs case, update only priority.
+    else if (new_priority != -1 && strcmp("", new_name) == 0){
+        strcpy(query, "UPDATE teams SET priority = ? WHERE id = ?");
+        parameters[0] = malloc(4);
+        sprintf(parameters[0], "%d", new_priority);
+        parameters[1] = malloc(sizeof(char) * strlen(argv[0]));
+        strcpy(parameters[1], argv[0]);
+    }
+
+    // Update teams
+    if (SUCCESS != db_execute(query, parameters)){
+        fprintf(stderr, "Unable to edit team: %s\n", sqlite3_errmsg(db));
+        return(ERROR_DB_UPDATE_TEAMS);
+    }
+
+    // Inform about the newly created team
+    printf("Team ID %s updated.\n", argv[0]);
+    return(SUCCESS);
+}

+ 1 - 1
src/cli/runeoptimizer/update/update_json.c

@@ -370,8 +370,8 @@ static int update_json_rune(json_object *rune_json, unsigned char *unit_id){
     float max_efficiency = 0.0f;
     update_efficiency(query_parameters[0], &efficiency, &max_efficiency);
     strcpy(query_parameters[2], query_parameters[0]); //ID
+    query_parameters[0] = malloc(35);
     query_parameters[1] = malloc(35);
-    query_parameters[2] = malloc(35);
     sprintf(query_parameters[0], "%3.2f", efficiency);
     sprintf(query_parameters[1], "%3.2f", max_efficiency);
     if (

+ 34 - 0
src/gui/runeoptimizer_gui/data/data.py

@@ -16,5 +16,39 @@ RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
 
 """
 
+from entity.Team import Team
+from entity.Unit import Unit
+import database.database as database
+
 UNITS = {}
 TEAMS = {}
+
+def reload_units():
+    # Get all the units, sorted by priority desc
+    global UNITS
+    UNITS = {}
+    cursor = database.CONNECTION.execute("""
+      SELECT
+        id,
+        (
+          SELECT sum(priority)
+          FROM teams
+          WHERE id IN (SELECT team FROM units_teams WHERE unit = units.id)
+        ) AS prio
+      FROM units
+      ORDER BY prio DESC;
+    """)
+    rows = cursor.fetchall()
+    for row in rows:
+        UNITS[str(row[0])] = Unit(str(row[0]))
+
+def reload_teams():
+    # Get all the teams, sorted by priority desc
+    global TEAMS
+    TEAMS = {}
+    cursor = database.CONNECTION.execute(
+      "SELECT id FROM teams ORDER BY priority DESC;"
+    )
+    rows = cursor.fetchall()
+    for row in rows:
+        TEAMS[str(row[0])] = Team(str(row[0]))

+ 9 - 2
src/gui/runeoptimizer_gui/entity/StatSet.py

@@ -49,11 +49,18 @@ class StatSet():
     acc : int
         Accuracy stat. Between 0 and 85, both included.
     ehp : int
-        Effective HP stat. Depends on HP and DEF. Always equal or greater than 1.
+        Effective HP stat. Depends on HP and DEF. Equal or greater than 1.
     dmg : int
-        Damage stat. Depends on ATK, CRR and CRD. Always equal or greater than 1.
+        Damage stat. Depends on ATK, CRR and CRD. Equal or greater than 1.
 
+    Methods
+    -------
+
+    values(self) : int[]
+        Gets all stats as an array. The order is: HP, ATK, DFC, SPD, CRR, CRD,
+        RES, ACC, EHP, DMG
     """
+
     _hp  = 0
     _atk = 0
     _dfc = 0

+ 55 - 39
src/gui/runeoptimizer_gui/gui/PanelInfo.py

@@ -44,6 +44,12 @@ class PanelInfo(wx.Panel):
         # Parent constructor
         wx.Panel.__init__(self, parent=parent, id=id)
 
+        # Organize sizers
+        panel_sizer = wx.BoxSizer(wx.VERTICAL)
+        info_sizer = wx.FlexGridSizer(0, 2, (20, 10))
+        #info_sizer = wx.StaticBoxSizer(wx.VERTICAL, self, "INFO")
+
+
         # Prepare some fonts
         title_font = wx.Font(
           pointSize=14, family=wx.FONTFAMILY_DEFAULT,
@@ -58,43 +64,53 @@ class PanelInfo(wx.Panel):
           style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
         )
 
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label=values.APP_INFO["name"],
-          pos=(30, 30), size=(400, 40)
-        ).SetFont(title_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label="Application version:",
-          pos=(40, 90), size=(150, 30), style=wx.ALIGN_RIGHT
-        ).SetFont(label_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label=values.APP_INFO["app_version"],
-          pos=(200, 90), size=(400, 30)
-        ).SetFont(content_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label="GUI version:",
-          pos=(40, 120), size=(150, 30), style=wx.ALIGN_RIGHT
-        ).SetFont(label_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label=values.APP_INFO["gui_version"],
-          pos=(200, 120), size=(400, 30)
-        ).SetFont(content_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY, label="Author:",
-          pos=(40, 150), size=(150, 30), style=wx.ALIGN_RIGHT
-        ).SetFont(label_font)
-        wx.StaticText(
-          parent=self, id=wx.ID_ANY,
-          label=values.APP_INFO["author"] + " <" +values.APP_INFO["author_mail"] + ">",
-          pos=(200, 150), size=(400, 30)
-        ).SetFont(content_font)
-        y = 180
+        title_label = wx.StaticText(self, label=values.APP_INFO["name"])
+        title_label.SetFont(title_font)
+        panel_sizer.Add(title_label, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.BOTTOM, 30)
+
+        info_sizer.Add(wx.StaticText(self, size=(200, 0)), 0)
+        info_sizer.Add(wx.StaticText(self, size=(400, 0)), 0)
+        label = wx.StaticText(
+          self, label="Application version:", style=wx.ALIGN_RIGHT
+        )
+        label.SetFont(label_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+        label = wx.StaticText(self, label=values.APP_INFO["app_version"])
+        label.SetFont(content_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+
+        label = wx.StaticText(
+          self, label="GUI version:", style=wx.ALIGN_RIGHT
+        )
+        label.SetFont(label_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+        label = wx.StaticText(self, label=values.APP_INFO["gui_version"])
+        label.SetFont(content_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+        label = wx.StaticText(self, label="Author:", style=wx.ALIGN_RIGHT)
+        label.SetFont(label_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+        label = wx.StaticText(
+          self, label=(
+            values.APP_INFO["author"]
+            + " <" + values.APP_INFO["author_mail"] + ">"
+          )
+        )
+        label.SetFont(content_font)
+        info_sizer.Add(label, 1, wx.EXPAND)
+
         for name, url in values.APP_INFO["url"].items():
-            wx.StaticText(
-              parent=self, id=wx.ID_ANY, label=name + ":",
-              pos=(40, y), size=(150, 30), style=wx.ALIGN_RIGHT
-            ).SetFont(label_font)
-            wx.adv.HyperlinkCtrl(
-                parent=self, id=wx.ID_ANY, label=url,
-                pos=(200-8, y-5), size=(400, 30), style=wx.adv.HL_ALIGN_LEFT
-            ).SetFont(content_font)
-            y += 30
+            label = wx.StaticText(
+              self, label=name + ":", style=wx.ALIGN_RIGHT
+            )
+            label.SetFont(label_font)
+            info_sizer.Add(label, 1, wx.EXPAND)
+            label = wx.adv.HyperlinkCtrl(
+                self, label=url, style=wx.adv.HL_ALIGN_LEFT
+            )
+            label.SetFont(content_font)
+            info_sizer.Add(label, 1, wx.EXPAND)
+
+        panel_sizer.Add(info_sizer, 1, wx.ALIGN_CENTER)
+        self.SetSizer(panel_sizer)
+        self.Layout()

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 290 - 651
src/gui/runeoptimizer_gui/gui/PanelOptimizer.py


+ 203 - 424
src/gui/runeoptimizer_gui/gui/PanelResults.py

@@ -98,476 +98,249 @@ class PanelResults(wx.Panel):
           style=wx.FONTSTYLE_ITALIC, weight=wx.FONTWEIGHT_NORMAL
         )
 
-        # Contains the results table and unit details. Hidden until they are
-        # loaded.
-        self._table_sizer = wx.BoxSizer(wx.VERTICAL)
-        # Contains all thigs to be shown once a result is selected
-        self._details_sizer = wx.BoxSizer(wx.VERTICAL)
+        # Sizer diagram
+        # panel_sizer (V)
+        # |-_empty_sizer (H)
+        # |-_top_sizer (H)
+        # | |-unit_sizer (V)
+        # | |-results_sizer (H)
+        # |   |-results_buttons_sizer (V)
+        # |-_bottom_sizer (H)
+        # | |-stats_sizer (V)
+        # | |-runes_sizer (G)
+
+
+        panel_sizer = wx.BoxSizer(wx.VERTICAL)
+        self._empty_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        #self._top_sizer = wx.StaticBoxSizer(wx.HORIZONTAL, self, "self._top_sizer")
+        self._top_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        #unit_sizer = wx.StaticBoxSizer(wx.VERTICAL, self._top_sizer.GetStaticBox(), "unit_sizer")
+        unit_sizer = wx.BoxSizer(wx.VERTICAL)
+        #results_sizer = wx.StaticBoxSizer(wx.HORIZONTAL, self._top_sizer.GetStaticBox(), "results_sizer")
+        results_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        #results_buttons_sizer = wx.StaticBoxSizer(wx.VERTICAL, results_sizer.GetStaticBox(), "results_buttons_sizer")
+        results_buttons_sizer = wx.BoxSizer(wx.VERTICAL)
+        #self._bottom_sizer = wx.StaticBoxSizer(wx.HORIZONTAL, self, "self._bottom_sizer")
+        self._bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        #stats_sizer = wx.StaticBoxSizer(wx.VERTICAL, self._bottom_sizer.GetStaticBox(), "stats_sizer")
+        stats_sizer = wx.BoxSizer(wx.VERTICAL)
+        runes_sizer = wx.FlexGridSizer(2, 3, (3, 3))
+
+        # As much as possible, create sizer hierarchy now (reverse order):
+        self._bottom_sizer.Add(stats_sizer, 0, wx.EXPAND|wx.ALIGN_TOP|wx.LEFT, 20)
+        self._bottom_sizer.Add(
+          runes_sizer, 0, wx.EXPAND|wx.ALIGN_TOP|wx.LEFT|wx.BOTTOM, 20
+        )
+        # After adding the result table
+        #results_sizer.Add(results_buttons_sizer, 0, wx.ALIGN_CENTRE_HORIZONTAL|wx.LEFT, 10)
+        self._top_sizer.Add(unit_sizer, 1, wx.EXPAND|wx.LEFT, 20)
+        self._top_sizer.Add(results_sizer, 0, wx.LEFT, 10)
+        panel_sizer.Add(self._empty_sizer, 1, wx.EXPAND)
+        panel_sizer.Add(self._top_sizer, 1, wx.EXPAND)
+        panel_sizer.Add(self._bottom_sizer, 1, wx.EXPAND|wx.TOP, 20)
+        self.SetSizer(panel_sizer)
+
+        # Message for no optimization
+        empty_label = wx.StaticText(
+            self, label="Nothing yet.\nOptimize something!"
+        )
+        empty_label.SetFont(title_font)
+        self._empty_sizer.Add(empty_label, 1, wx.EXPAND, wx.ALL, 50)
 
         # Unit name, or placeholder message
-        self._unit_name_label = wx.StaticText(
-            parent=self, id=wx.ID_ANY, label="Nothing yet.\nOptimize something!",
-            pos=(10, 0), size=(400, 100)
-        )
+        self._unit_name_label = wx.StaticText(self, size=(220, 45))
         self._unit_name_label.SetFont(title_font)
+        unit_sizer.Add(self._unit_name_label, 0)
 
         #Unit details
-        self._stars_label = wx.StaticText(
-          parent=self, label="", pos=(20, 30), size=(180, 100)
-        )
+        self._stars_label = wx.StaticText(self)
         self._stars_label.SetFont(title_font)
-        self._table_sizer.Add(self._stars_label)
-        self._level_label = wx.StaticText(
-          parent=self, label="", pos=(20, 60), size=(180, 100)
-        )
-        self._table_sizer.Add(self._level_label)
+        unit_sizer.Add(self._stars_label, 0, wx.EXPAND|wx.LEFT|wx.TOP, 15)
+        self._level_label = wx.StaticText(self)
         self._level_label.SetFont(subtitle_font)
-        self._priority_label = wx.StaticText(
-          parent=self, label="", pos=(20, 90), size=(180, 100)
-        )
+        unit_sizer.Add(self._level_label, 0, wx.EXPAND|wx.LEFT, 15)
+        self._priority_label = wx.StaticText(self)
         self._priority_label.SetFont(subtitle_font)
-        self._table_sizer.Add(self._priority_label)
-        self._id_label = wx.StaticText(
-          parent=self, label="", pos=(20, 120), size=(180, 100)
-        )
+        unit_sizer.Add(self._priority_label, 0, wx.EXPAND|wx.LEFT, 15)
+        self._id_label = wx.StaticText(self)
         self._id_label.SetFont(subtitle_font)
-        self._table_sizer.Add(self._id_label)
+        unit_sizer.Add(self._id_label, 0, wx.EXPAND|wx.LEFT, 15)
 
         # The result list table
-        self._result_grid = wx.grid.Grid(
-          parent=self, id=wx.ID_ANY, pos=(275, 0), size=(525, 170)
-        )
-        self._result_grid.CreateGrid(
-          numRows=10, numCols=11
-        )
+        self._result_grid = wx.grid.Grid(self)
+        self._result_grid.CreateGrid(10, 11)
         self._result_grid.EnableEditing(False)
-        self._result_grid.SetSelectionMode(wx.grid.Grid.GridSelectionModes.SelectRows)
+        self._result_grid.SetSelectionMode(wx.grid.Grid.SelectRows)
         self._result_grid.SetDefaultCellAlignment(
-          horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE
+          wx.ALIGN_RIGHT, wx.ALIGN_CENTRE
         )
-        monospace_font.PointSize -= 1
         self._result_grid.SetDefaultCellFont(monospace_font)
-        monospace_font.PointSize += 1
-        self._result_grid.SetRowLabelSize(width=35)
-        self._result_grid.SetColLabelValue(col=0, value="Rating")
-        self._result_grid.SetColSize(col=0, width=50)
-        self._result_grid.SetColLabelValue(col=1, value="HP")
-        self._result_grid.SetColSize(col=1, width=50)
-        self._result_grid.SetColLabelValue(col=2, value="ATK")
-        self._result_grid.SetColSize(col=2, width=40)
-        self._result_grid.SetColLabelValue(col=3, value="DEF")
-        self._result_grid.SetColSize(col=3, width=40)
-        self._result_grid.SetColLabelValue(col=4, value="SPD")
-        self._result_grid.SetColSize(col=4, width=40)
-        self._result_grid.SetColLabelValue(col=5, value="CRR")
-        self._result_grid.SetColSize(col=5, width=40)
-        self._result_grid.SetColLabelValue(col=6, value="CRD")
-        self._result_grid.SetColSize(col=6, width=40)
-        self._result_grid.SetColLabelValue(col=7, value="RES")
-        self._result_grid.SetColSize(col=7, width=40)
-        self._result_grid.SetColLabelValue(col=8, value="ACC")
-        self._result_grid.SetColSize(col=8, width=40)
-        self._result_grid.SetColLabelValue(col=9, value="EHP")
-        self._result_grid.SetColSize(col=9, width=60)
-        self._result_grid.SetColLabelValue(col=10, value="DMG")
-        self._result_grid.SetColSize(col=10, width=50)
-        self._result_grid.SetColLabelSize(height=20)
-        self._result_grid.SetDefaultRowSize(height=15)
+        self._result_grid.SetDefaultRowSize(15)
+        self._result_grid.SetRowLabelSize(35)
+        self._result_grid.SetColLabelValue(0, "Rating")
+        self._result_grid.SetColSize(0, 50)
+        self._result_grid.SetColLabelValue(1, "HP")
+        self._result_grid.SetColSize(1, 50)
+        self._result_grid.SetColLabelValue(2, "ATK")
+        self._result_grid.SetColSize(2, 40)
+        self._result_grid.SetColLabelValue(3, "DEF")
+        self._result_grid.SetColSize(3, 40)
+        self._result_grid.SetColLabelValue(4, "SPD")
+        self._result_grid.SetColSize(4, 40)
+        self._result_grid.SetColLabelValue(5, "CRR")
+        self._result_grid.SetColSize(5, 40)
+        self._result_grid.SetColLabelValue(6, "CRD")
+        self._result_grid.SetColSize(6, 40)
+        self._result_grid.SetColLabelValue(7, "RES")
+        self._result_grid.SetColSize(7, 40)
+        self._result_grid.SetColLabelValue(8, "ACC")
+        self._result_grid.SetColSize(8, 40)
+        self._result_grid.SetColLabelValue(9, "EHP")
+        self._result_grid.SetColSize(9, 60)
+        self._result_grid.SetColLabelValue(10, "DMG")
+        self._result_grid.SetColSize(10, 50)
+        self._result_grid.SetColLabelSize(20)
         self._result_grid.Bind(
           wx.grid.EVT_GRID_SELECT_CELL, self._result_selected
         )
-        self._table_sizer.Add(self._result_grid)
+        results_sizer.Add(self._result_grid, 0, wx.LEFT|wx.RIGHT, 10)
 
         # Paginator
         self._page_prev_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(805, 0), size=(40, 50),
-          style=wx.LC_REPORT, label="Prev\npage"
+          self, size=(40, 50), label="Prev\npage"
         )
         self._page_prev_button.Bind(wx.EVT_BUTTON, self._page_prev)
-        self._table_sizer.Add(self._page_prev_button)
+        results_buttons_sizer.Add(self._page_prev_button, 0)
         self._page_label = wx.StaticText(
-          parent=self,id=wx.ID_ANY, pos=(805, 50), size=(40, 15),
-          style=wx.ALIGN_CENTRE_HORIZONTAL, label="1/1"
+          self, size=(40, 15), style=wx.ALIGN_CENTRE_HORIZONTAL, label="1/1"
+        )
+        results_buttons_sizer.Add(
+          self._page_label, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 10
         )
-        self._table_sizer.Add(self._page_label)
         self._page_next_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(805, 70), size=(40, 50),
-          style=wx.LC_REPORT, label="Next\npage"
+          self, size=(40, 50), label="Next\npage"
         )
         self._page_next_button.Bind(wx.EVT_BUTTON, self._page_next)
-        self._table_sizer.Add(self._page_next_button)
+        results_buttons_sizer.Add(self._page_next_button)
+        results_sizer.Add(
+          results_buttons_sizer, 0, wx.ALIGN_CENTRE|wx.RIGHT, 30
+        )
 
         # Stats table
-        self._stat_grid = wx.grid.Grid(
-          parent=self, id=wx.ID_ANY, pos=(40, 210),
-          size=(275, 220), style=wx.LC_REPORT
-        )
-        self._stat_grid.CreateGrid(numRows=10, numCols=4)
+        self._stat_grid = wx.grid.Grid(self, style=wx.LC_REPORT)
+        self._stat_grid.CreateGrid(10, 4)
         self._stat_grid.EnableEditing(False)
         self._stat_grid.SetDefaultCellAlignment(
-          horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE
+          wx.ALIGN_RIGHT, wx.ALIGN_CENTRE
         )
         self._stat_grid.SetDefaultCellFont(monospace_font)
-        self._stat_grid.SetColSize(col=0, width=60)
-        self._stat_grid.SetColSize(col=1, width=60)
-        self._stat_grid.SetColSize(col=2, width=60)
-        self._stat_grid.SetColSize(col=3, width=60)
-        self._stat_grid.SetColLabelValue(col=0, value="Base")
-        self._stat_grid.SetColLabelValue(col=1, value="Curr.")
-        self._stat_grid.SetColLabelValue(col=2, value="After")
-        self._stat_grid.SetColLabelValue(col=3, value="Diff.")
-        self._stat_grid.SetRowLabelSize(width=35)
-        self._stat_grid.SetColLabelSize(height=20)
-        self._stat_grid.SetRowSize(row=0, height=20)
-        self._stat_grid.SetRowSize(row=1, height=20)
-        self._stat_grid.SetRowSize(row=2, height=20)
-        self._stat_grid.SetRowSize(row=3, height=20)
-        self._stat_grid.SetRowSize(row=4, height=20)
-        self._stat_grid.SetRowSize(row=5, height=20)
-        self._stat_grid.SetRowSize(row=6, height=20)
-        self._stat_grid.SetRowSize(row=7, height=20)
-        self._stat_grid.SetRowSize(row=8, height=20)
-        self._stat_grid.SetRowSize(row=9, height=20)
-        self._stat_grid.SetRowLabelValue(row=0, value=" HP")
-        self._stat_grid.SetRowLabelValue(row=1, value="ATK")
-        self._stat_grid.SetRowLabelValue(row=2, value="DEF")
-        self._stat_grid.SetRowLabelValue(row=3, value="SPD")
-        self._stat_grid.SetRowLabelValue(row=4, value="CRR")
-        self._stat_grid.SetRowLabelValue(row=5, value="CRD")
-        self._stat_grid.SetRowLabelValue(row=6, value="RES")
-        self._stat_grid.SetRowLabelValue(row=7, value="ACC")
-        self._stat_grid.SetRowLabelValue(row=8, value="EHP")
-        self._stat_grid.SetRowLabelValue(row=9, value="DMG")
-        self._details_sizer.Add(self._stat_grid)
-
-        #Rune set list
-        rune_box_list = [
-          wx.StaticBox(
-            parent=self, label="Slot1:",id=wx.ID_ANY,
-            pos=(530, 210), size=(130, 185)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot2:",id=wx.ID_ANY,
-            pos=(680, 210), size=(130, 185)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot3:",id=wx.ID_ANY,
-            pos=(680, 400), size=(130, 185)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot4:",id=wx.ID_ANY,
-            pos=(530, 400), size=(130, 185)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot5:",id=wx.ID_ANY,
-            pos=(380, 400), size=(130, 185)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot6:",id=wx.ID_ANY,
-            pos=(380, 210), size=(130, 185)
-          )
-        ]
-        self._set_label_list = [
-          wx.StaticText(
-            rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-        ]
-        self._id_label_list = [
-          wx.StaticText(
-            rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          )
-        ]
-        self._main_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-        ]
-        self._innate_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          )
-        ]
-
-        self._stat_label_list = [
-          [
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+        self._stat_grid.SetColSize(0, 60)
+        self._stat_grid.SetColSize(1, 60)
+        self._stat_grid.SetColSize(2, 60)
+        self._stat_grid.SetColSize(3, 60)
+        self._stat_grid.SetColLabelValue(0, "Base")
+        self._stat_grid.SetColLabelValue(1, "Curr.")
+        self._stat_grid.SetColLabelValue(2, "After")
+        self._stat_grid.SetColLabelValue(3, "Diff.")
+        self._stat_grid.SetRowLabelSize(35)
+        self._stat_grid.SetColLabelSize(20)
+        self._stat_grid.SetRowSize(0, 20)
+        self._stat_grid.SetRowSize(1, 20)
+        self._stat_grid.SetRowSize(2, 20)
+        self._stat_grid.SetRowSize(3, 20)
+        self._stat_grid.SetRowSize(4, 20)
+        self._stat_grid.SetRowSize(5, 20)
+        self._stat_grid.SetRowSize(6, 20)
+        self._stat_grid.SetRowSize(7, 20)
+        self._stat_grid.SetRowSize(8, 20)
+        self._stat_grid.SetRowSize(9, 20)
+        self._stat_grid.SetRowLabelValue(0, " HP")
+        self._stat_grid.SetRowLabelValue(1, "ATK")
+        self._stat_grid.SetRowLabelValue(2, "DEF")
+        self._stat_grid.SetRowLabelValue(3, "SPD")
+        self._stat_grid.SetRowLabelValue(4, "CRR")
+        self._stat_grid.SetRowLabelValue(5, "CRD")
+        self._stat_grid.SetRowLabelValue(6, "RES")
+        self._stat_grid.SetRowLabelValue(7, "ACC")
+        self._stat_grid.SetRowLabelValue(8, "EHP")
+        self._stat_grid.SetRowLabelValue(9, "DMG")
+        stats_sizer.Add(self._stat_grid, 0)
+
+        # Rune apply button
+        apply_button = wx.Button(self, label="Apply runes")
+        apply_button.Bind(wx.EVT_BUTTON, self._apply_runes)
+        stats_sizer.Add(apply_button, 0, wx.ALIGN_CENTRE|wx.TOP, 20)
+
+        #Rune boxes
+        rune_box_list = []
+        self._set_label_list = []
+        self._id_label_list = []
+        self._main_label_list = []
+        self._innate_label_list = []
+        self._stat_label_list = [[], [], [], [], [], []]
+        self._eff_label_list = []
+        self._location_label_list = []
+        for i in range(0, 6):
+            rune_box_list.append(
+              wx.StaticBoxSizer(wx.VERTICAL, self, label="Slot " + str(i))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            self._set_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._set_label_list[i], 0, wx.LEFT, 3)
+            self._id_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._id_label_list[i], 0, wx.LEFT, 3)
+            rune_box_list[i].Add(
+              wx.StaticLine(rune_box_list[i].GetStaticBox()), 0, wx.EXPAND
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            self._main_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._main_label_list[i], 0, wx.LEFT, 3)
+            self._innate_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 18))
             )
-          ],
-        ]
-        self._eff_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 133), size=(130, 10)
-          ),
-        ]
-        self._location_label_list = [
-          wx.StaticText(
-            rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-          wx.StaticText(
-            rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-          wx.StaticText(
-            rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-          wx.StaticText(
-            rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-          wx.StaticText(
-            rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-          wx.StaticText(
-            rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 150), size=(130, 30)
-          ),
-        ]
-
-        for i in range(0, 6):
-            self._details_sizer.Add(rune_box_list[i])
-            # Separators
-            wx.StaticLine(
-              parent=rune_box_list[i], id=wx.ID_ANY,
-              pos=(0, 30), size=(130, 1), style=wx.LC_REPORT
+            rune_box_list[i].Add(self._innate_label_list[i], 0, wx.LEFT, 3)
+            for j in range(0, 4):
+                self._stat_label_list[i].append(
+                  wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
+                )
+                rune_box_list[i].Add(
+                  self._stat_label_list[i][j], 0, wx.LEFT, 3
+                )
+            rune_box_list[i].Add(
+              wx.StaticLine(rune_box_list[i].GetStaticBox()), 0, wx.EXPAND
+            )
+            self._eff_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
             )
-            wx.StaticLine(
-              parent=rune_box_list[i], id=wx.ID_ANY,
-              pos=(0, 130), size=(130, 1), style=wx.LC_REPORT
+            rune_box_list[i].Add(
+              self._eff_label_list[i], 0, wx.LEFT|wx.BOTTOM, 3
             )
-            wx.StaticLine(
-              parent=rune_box_list[i], id=wx.ID_ANY,
-              pos=(0, 150), size=(130, 1), style=wx.LC_REPORT
+            rune_box_list[i].Add(
+              wx.StaticLine(rune_box_list[i].GetStaticBox()), 0, wx.EXPAND
             )
+            self._location_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 15))
+            )
+            rune_box_list[i].Add(
+              self._location_label_list[i], 0, wx.LEFT|wx.BOTTOM, 3
+            )
+
             # Set fonts
-            rune_box_list[i].SetFont(monospace_font)
+            rune_box_list[i].GetStaticBox().SetFont(monospace_font)
             self._innate_label_list[i].SetFont(monospace_font_italic)
             self._main_label_list[i].SetFont(monospace_font_bold)
             self._eff_label_list[i].SetFont(monospace_font_italic)
             self._location_label_list[i].SetFont(monospace_font_bold)
-
-        # Rune apply button
-        apply_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(100, 500), size=(165, 60),
-          style=wx.LC_REPORT, label="Apply runes"
-        )
-        apply_button.Bind(wx.EVT_BUTTON, self._apply_runes)
-        self._details_sizer.Add(apply_button)
+            runes_sizer.Add(rune_box_list[i], 0)
 
         # By default, hide everything TODO
-        self._table_sizer.ShowItems(False)
-        self._details_sizer.ShowItems(False)
+        self._top_sizer.ShowItems(False)
+        self._bottom_sizer.ShowItems(False)
+        self.Layout()
 
     def _update_base_and_current_stats(self):
         """
@@ -797,7 +570,10 @@ class PanelResults(wx.Panel):
         self._id_label.SetLabel("#" + self._unit.id)
 
         # Make the table visible
-        self._table_sizer.ShowItems(True)
+        self._empty_sizer.ShowItems(False)
+        self._top_sizer.ShowItems(True)
+        #self._bottom_sizer.ShowItems(False)
+        self.Layout()
 
     def _result_selected(self, event=None):
         """
@@ -818,7 +594,8 @@ class PanelResults(wx.Panel):
           selected_line + (self._lines_per_page * self._page)
         if self._selected_result_index >= len(self._results.results):
             self._selected_result_index = -1;
-            self._details_sizer.ShowItems(False)
+            self._bottom_sizer.ShowItems(False)
+            self.Layout()
             return
         self._stat_grid.SetCellValue(
           row=0, col=2,
@@ -1069,7 +846,8 @@ class PanelResults(wx.Panel):
         else:
             self._stat_grid.SetCellValue(row=9, col=3, s="")
 
-        self._details_sizer.ShowItems(True)
+        self._bottom_sizer.ShowItems(True)
+        self.Layout()
 
         # Clean the runes
         for i in range(0, 5):
@@ -1192,7 +970,8 @@ class PanelResults(wx.Panel):
             i = i + 1
 
         # Make the info visible
-        self._details_sizer.ShowItems(True)
+        self._bottom_sizer.ShowItems(True)
+        self.Layout()
 
     def _recalculte_stats_of_modified_units(self):
         """

+ 183 - 158
src/gui/runeoptimizer_gui/gui/PanelTeams.py

@@ -19,6 +19,7 @@ import wx
 import wx.grid
 import database.database as database
 import data.data as data
+import runeoptimizer.runeoptimizer as runeoptimizer
 from gui.DialogConfirm import DialogConfirm
 from gui.DialogNewTeam import DialogNewTeam
 
@@ -66,148 +67,165 @@ class PanelTeams(wx.Panel):
 
         # Prepare some fonts
         bold_font = wx.Font(
-          pointSize=10, family=wx.FONTFAMILY_DEFAULT,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
+          10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
         )
-
-        team_list_box = wx.StaticBox(
-          self, label="Select unit:", id=wx.ID_ANY, pos=(0, 0), size=(270, 590)
+        
+        # Sizer diagrams:
+        # panel_sizer (V)
+        #   team_list_sizer (V)
+        #   _team_details_sizer (V)
+        #     team_info_sizer (G)
+        #     team_unit_manage_sizer (H)
+        #       team_unit_list_sizer (V)
+        #       team_unit_button_sizer (V)
+        #       team_unit_all_sizer (V)
+        #         team_unit_filter_sizer (V)
+        
+        # Create and organize sizers
+        panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        team_list_sizer = wx.StaticBoxSizer(wx.VERTICAL, self, "Select team:")
+        self._team_details_sizer = wx.BoxSizer(wx.VERTICAL)
+        team_info_sizer = wx.FlexGridSizer(2, 2, (10, 20))
+        team_unit_manage_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        team_unit_list_sizer = wx.StaticBoxSizer(
+          wx.VERTICAL, self, "Units in team:"
+        )
+        team_unit_button_sizer = wx.BoxSizer(wx.VERTICAL)
+        team_unit_all_sizer = wx.StaticBoxSizer(
+          wx.VERTICAL, self, "Other units:"
+        )
+        team_unit_manage_sizer.Add(team_unit_list_sizer, 1, wx.EXPAND)
+        team_unit_manage_sizer.Add(team_unit_button_sizer, 1, wx.EXPAND)
+        team_unit_manage_sizer.Add(team_unit_all_sizer, 1, wx.EXPAND)
+        self._team_details_sizer.Add(team_info_sizer, 0)
+        self._team_details_sizer.Add(
+          team_unit_manage_sizer, 1, wx.EXPAND|wx.TOP, 20
         )
-        wx.StaticText(
-          parent=team_list_box, id=wx.ID_ANY,
-          label="Name                             Prio.",
-          pos=(10, 10), size=(240, 20)
-        ).SetFont(bold_font)
+        panel_sizer.Add(team_list_sizer, 0, wx.EXPAND)
+        panel_sizer.Add(self._team_details_sizer, 0, wx.EXPAND|wx.LEFT, 20)
+        self.SetSizer(panel_sizer)
+
         self._team_list = wx.ListCtrl(
-          parent=team_list_box, id=wx.ID_ANY, pos=(10, 30), size=(250, 460),
-          style=wx.LC_REPORT|wx.LC_NO_HEADER
+          team_list_sizer.GetStaticBox(), style=wx.LC_REPORT, size=(240, 0)
         )
         self._team_list.InsertColumn(0, "Name", width=200)
         self._team_list.InsertColumn(1, "Prio.", width=40)
-        self._populate_team_list(event=None)
+        self._populate_team_list(None)
         self._team_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self._team_selected)
+        team_list_sizer.Add(self._team_list, 1, wx.EXPAND)
 
         new_team_button = wx.Button(
-          parent=team_list_box, id=wx.ID_ANY, pos=(50, 510), size=(170, 40),
-          style=wx.LC_REPORT, label="New team"
+          team_list_sizer.GetStaticBox(), size=(170, 40), label="New team"
         )
         new_team_button.Bind(wx.EVT_BUTTON, self._create_team)
-
-        # Sizer for all team details. Will be hidden until a team is selected
-        self._details_sizer = wx.BoxSizer(wx.VERTICAL)
+        team_list_sizer.Add(
+          new_team_button, 0, wx.TOP|wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 15
+        )
 
         # Team title and priority editors
+        team_info_sizer.Add(
+          wx.StaticText(self, label="Team name:"), 0, wx.ALIGN_CENTER_VERTICAL
+        )
         self._title_text = wx.TextCtrl(
-          parent=self, id=wx.ID_ANY,
-          pos=(300, 30), size=(200, 25), style=wx.TE_RICH|wx.TE_MULTILINE
+          self, size=(200, 25), style=wx.TE_RICH|wx.TE_MULTILINE
         )
-        self._details_sizer.Add(self._title_text)
         self._title_text.Bind(wx.EVT_TEXT, self._change_title);
-        self._details_sizer.Add(
-          wx.StaticText(
-            parent=self, id=wx.ID_ANY, label="Priority:",
-            pos=(310, 80), size=(80, 30)
-          )
+        team_info_sizer.Add(self._title_text)
+        team_info_sizer.Add(
+          wx.StaticText(self, label="Priority:"), 0, wx.ALIGN_CENTER_VERTICAL
         )
         self._priority_text = wx.TextCtrl(
-          parent=self, id=wx.ID_ANY,
-          pos=(360, 80), size=(30, 25), style=wx.TE_RICH|wx.TE_MULTILINE)
-        self._details_sizer.Add(self._priority_text)
+          self, size=(30, 25), style=wx.TE_RICH|wx.TE_MULTILINE
+        )
         self._priority_text.Bind(wx.EVT_TEXT, self._change_priority);
+        team_info_sizer.Add(self._priority_text, 0, wx.ALIGN_CENTER_VERTICAL)
 
         # Team unit list
-        team_units_box = wx.StaticBox(
-          parent=self, label="Units in team:",
-          id=wx.ID_ANY, pos=(300, 130), size=(150, 380)
-        )
         self._team_unit_list = wx.ListCtrl(
-          parent=team_units_box, id=wx.ID_ANY, pos=(5, 5), size=(140, 360),
-          style=wx.LC_REPORT|wx.LC_NO_HEADER
+          team_unit_list_sizer.GetStaticBox(), style=wx.LC_REPORT,
+          size=(140, 0)
         )
-        self._details_sizer.Add(team_units_box)
         self._team_unit_list.InsertColumn(0, "Name", width=140)
-        self._team_unit_list.InsertColumn(1, "Level", width=140)
+        self._team_unit_list.InsertColumn(1, "ID", width=0)
+        team_unit_list_sizer.Add(self._team_unit_list, 1, wx.EXPAND)
+        
+        add_button = wx.Button(
+          self, size=(150, 40), label="<<<    Add    <<<"
+        )
+        team_unit_button_sizer.Add(add_button, 0, wx.EXPAND|wx.TOP, 50)
+        add_button.Bind(wx.EVT_BUTTON, self._add_to_team)
+        remove_button = wx.Button(
+          self, size=(150, 40), label=">>>   Remove   >>>"
+        )
+        team_unit_button_sizer.Add(remove_button, 0, wx.EXPAND|wx.TOP, 10)
+        remove_button.Bind(wx.EVT_BUTTON, self._remove_from_team)
 
-        # All unit selector
-        all_units_box = wx.StaticBox(
-          parent=self, label="Other units:",
-          id=wx.ID_ANY, pos=(600, 130), size=(210, 380)
+        delete_button = wx.Button(self, size=(100, 40), label="Delete team")
+        team_unit_button_sizer.Add(0, 0, 1)
+        team_unit_button_sizer.Add(
+          delete_button, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.BOTTOM, 10
         )
-        self._details_sizer.Add(all_units_box)
-        wx.StaticText(
-          parent=all_units_box, id=wx.ID_ANY,
-          label="Name             Prio.   Sto.",
-          pos=(10, 10), size=(190, 20)
-        ).SetFont(bold_font)
+        delete_button.Bind(wx.EVT_BUTTON, self._delete_team)
 
+        # All unit selector
         self._all_unit_list = wx.ListCtrl(
-          parent=all_units_box, id=wx.ID_ANY, pos=(10, 30), size=(190, 165),
-          style=wx.LC_REPORT|wx.LC_NO_HEADER
+          team_unit_all_sizer.GetStaticBox(), style=wx.LC_REPORT,
+          size=(200, 0)
         )
-        self._details_sizer.Add(self._all_unit_list)
+        team_unit_all_sizer.Add(self._all_unit_list, 1, wx.EXPAND)
         self._all_unit_list.InsertColumn(0, "Name", width=120)
         self._all_unit_list.InsertColumn(1, "Prio.", width=40)
         self._all_unit_list.InsertColumn(2, "Sto.", width=30)
+        self._all_unit_list.InsertColumn(3, "ID.", width=0)
         # List filters
-        filter_box = wx.StaticBox(
-          parent=all_units_box, label="Filters:",id=wx.ID_ANY,
-          pos=(10, 200), size=(190, 150)
+        team_unit_filter_sizer = wx.StaticBoxSizer(
+          wx.VERTICAL, team_unit_all_sizer.GetStaticBox(), "Filers:"
         )
-        self._details_sizer.Add(filter_box)
-        wx.StaticText(
-          parent=filter_box, label="Monster name", pos=(5, 5), size=(180, 20)
+        team_unit_filter_sizer.Add(
+          wx.StaticText(
+            team_unit_filter_sizer.GetStaticBox(), label="Monster name"
+          ), 0, wx.LEFT, 5
         )
         self._filter_name_text = wx.TextCtrl(
-          parent=filter_box, id=wx.ID_ANY, value="", pos=(5, 25),
-          size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
+          team_unit_filter_sizer.GetStaticBox()
+        )
+        team_unit_filter_sizer.Add(
+          self._filter_name_text, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5
         )
         self._filter_name_text.Bind(wx.EVT_TEXT, self._populate_unit_list)
         self._filter_storage_check = wx.CheckBox(
-          parent=filter_box, id=wx.ID_ANY,
-          label="Monsters in storage", pos=(5, 55), size=(180, 20)
+          team_unit_filter_sizer.GetStaticBox(), label="Monsters in storage"
         )
+        team_unit_filter_sizer.Add(self._filter_storage_check)
         self._filter_storage_check.SetValue(True)
-        self._filter_storage_check.Bind(wx.EVT_CHECKBOX, self._populate_unit_list)
+        self._filter_storage_check.Bind(
+          wx.EVT_CHECKBOX, self._populate_unit_list
+        )
         self._filter_no_runes_check = wx.CheckBox(
-          parent=filter_box, id=wx.ID_ANY,
-          label="Monsters without runes", pos=(5, 75), size=(180, 20)
+          team_unit_filter_sizer.GetStaticBox(), label="Monsters without runes"
         )
+        team_unit_filter_sizer.Add(self._filter_no_runes_check)
         self._filter_no_runes_check.SetValue(True)
         self._filter_no_runes_check.Bind(
           wx.EVT_CHECKBOX, self._populate_unit_list
         )
         self._filter_no_teams_check = wx.CheckBox(
-          parent=filter_box, id=wx.ID_ANY,
-          label="Monsters not in teams", pos=(5, 95), size=(180, 20)
+          team_unit_filter_sizer.GetStaticBox(), label="Monsters not in teams"
         )
+        team_unit_filter_sizer.Add(self._filter_no_teams_check)
         self._filter_no_teams_check.SetValue(True)
         self._filter_no_teams_check.Bind(
           wx.EVT_CHECKBOX, self._populate_unit_list
         )
-        self._populate_unit_list(None)
-
-        add_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(450, 200), size=(150, 40),
-          style=wx.LC_REPORT, label="<<<    Add    <<<"
-        )
-        self._details_sizer.Add(add_button)
-        add_button.Bind(wx.EVT_BUTTON, self._add_to_team)
-        remove_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(450, 250), size=(150, 40),
-          style=wx.LC_REPORT, label=">>>   Remove   >>>"
+        team_unit_all_sizer.Add(
+          team_unit_filter_sizer, 0,
+          wx.TOP|wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 10
         )
-        self._details_sizer.Add(remove_button)
-        remove_button.Bind(wx.EVT_BUTTON, self._remove_from_team)
-
-        delete_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(300, 530), size=(150, 40),
-          style=wx.LC_REPORT, label="Delete team"
-        )
-        self._details_sizer.Add(delete_button)
-        delete_button.Bind(wx.EVT_BUTTON, self._delete_team)
+        self._populate_unit_list(None)
 
         # By default, hide all details
-        self._details_sizer.ShowItems(False)
+        self._team_details_sizer.ShowItems(False)
+        self.Layout()
 
     def _populate_team_list(self, event=None):
         """Populates the team list.
@@ -246,24 +264,30 @@ class PanelTeams(wx.Panel):
         opt_no_teams = self._filter_no_teams_check.GetValue()
         self._all_unit_list.DeleteAllItems()
         for unit in data.UNITS.values():
-            if opt_storage == False and unit.in_storage == True:
+            if not opt_storage and unit.in_storage:
                 continue
-            if opt_no_runes == False and unit.has_runes == False:
+            if not opt_no_runes and not unit.has_runes:
                 continue
-            if opt_no_teams == False and unit.in_teams == False:
+            if not opt_no_teams and not unit.in_teams:
                 continue
             if len(filter_name) > 0:
                 if filter_name.upper() not in unit.name.upper():
                     continue
+            already_in_team = False
+            if self._selected_team is not None:
+                for u_team in self._selected_team.units:
+                    # If the unit is in the currently selected team, skip
+                    if u_team.id == unit.id:
+                        already_in_team = True
+            if already_in_team:
+                continue
             self._all_unit_list.InsertItem(i, unit.name)
             self._all_unit_list.SetItem(i, 1, str(unit.priority))
             if (unit.in_storage):
                 self._all_unit_list.SetItem(i, 2, "X")
             else:
                 self._all_unit_list.SetItem(i, 2, " ")
-            # TODO Items can't hold too much data in Windows, store the first
-            # nine digits and rerieve it from database with LIKE
-            self._all_unit_list.SetItemData(i, int(unit.id))
+            self._all_unit_list.SetItem(i, 3, unit.id)
             i = i + 1
 
     def _populate_team_unit_list(self, event=None):
@@ -280,10 +304,9 @@ class PanelTeams(wx.Panel):
 
         i = 0
         self._team_unit_list.DeleteAllItems()
-        for unit in self._team_selected.units:
+        for unit in self._selected_team.units:
             self._team_unit_list.InsertItem(i, unit.name)
-            self._team_unit_list.SetItem(i, 1, "Lv." + str(unit.level))
-            self._team_unit_list.SetItemData(i, int(unit.id))
+            self._team_unit_list.SetItem(i, 1, unit.id)
             i = i + 1
 
     def _create_team(self, event=None):
@@ -297,7 +320,7 @@ class PanelTeams(wx.Panel):
         """
         with DialogNewTeam(parent=self) as dlg:
             if dlg.ShowModal() == wx.ID_OK:
-                reload_teams()
+                data.reload_teams()
                 self._populate_team_list()
 
     def _delete_team(self, event=None):
@@ -315,20 +338,17 @@ class PanelTeams(wx.Panel):
           self._team_selected.name + "?\n\nThis can't be undone."
         with DialogConfirm(parent=self, message=message) as dlg:
             if dlg.ShowModal() == wx.ID_OK:
-                # TODO: Do this with a command
-                cursor = DB_CONNECTION.execute(
-                  "DELETE FROM units_teams WHERE team = ?",
-                  (self._team_selected.id,))
-                cursor = DB_CONNECTION.execute(
-                  "DELETE FROM teams WHERE id = ?", (self._team_selected.id,)
+                ret = runeoptimizer.run(
+                  ["team", "delete", self._selected_team.id]
                 )
-                DB_CONNECTION.commit()
-                reload_teams()
+                data.reload_teams()
 
                 # Clear the selection and hide the details
-                self._details_sizer.ShowItems(False)
-                self._populate_team_list(event=None)
-                self.GetParent().panel_units.populate_unit_list(event=None)
+                self._team_details_sizer.ShowItems(False)
+                self.Layout()
+                self._populate_team_list()
+                self._selected_team = None
+                self.GetParent().panel_units.populate_unit_list()
 
     def _add_to_team(self, event=None):
         """Adds unit to the currently selected team.
@@ -342,24 +362,24 @@ class PanelTeams(wx.Panel):
             The event that triggered the call (default is None).
 
         """
-        selected_index = self._all_unit_list.GetFirstSelected()
-        while (selected_index != -1):
-            unitId = self._all_unit_list.GetItemData(selected_index)
-            # TODO: Do this with a command
-            query = "INSERT INTO units_teams (team, unit) VALUES (?, ?)";
-            cursor = DB_CONNECTION.execute(query, (self._team_selected.id, unitId))
-            selected_index = self._all_unit_list.GetNextSelected(selected_index)
-        DB_CONNECTION.commit()
-        reload_teams();
-        self._populate_team_unit_list(event=None)
-        self._populate_unit_list(event=None)
-        self.GetParent().panel_units.populate_unit_list(event=None)
+        sel_index = self._all_unit_list.GetFirstSelected()
+        while (sel_index != -1):
+            unit_id = self._all_unit_list.GetItem(sel_index, 3).GetText()
+            ret = runeoptimizer.run(
+              ["team", "add_unit", self._selected_team.id, unit_id]
+            )
+            sel_index = self._all_unit_list.GetNextSelected(sel_index)
+        data.reload_teams();
+        self._selected_team = data.TEAMS[self._selected_team.id]
+        self._populate_team_unit_list()
+        self._populate_unit_list()
+        self.GetParent().panel_units.populate_unit_list()
 
     def _remove_from_team(self, event=None):
         """Removes units the currently selected team.
 
-        Removes all the units selected in self._team_unit_list. Refreshes both unit
-        lists and also the unit list in the units panel.
+        Removes all the units selected in self._team_unit_list. Refreshes both
+        unit lists and also the unit list in the units panel.
 
         Parameters
         ----------
@@ -367,18 +387,18 @@ class PanelTeams(wx.Panel):
             The event that triggered the call (default is None).
 
         """
-        selected_index = self._team_unit_list.GetFirstSelected()
-        while (selected_index != -1):
-            unitId = self._team_unit_list.GetItemData(selected_index)
-            # TODO: Do this with a command
-            query = "DELETE FROM units_teams WHERE team = ? AND unit = ?";
-            cursor = database.CONNECTION.execute(query, (self._team_selected.id, unitId))
-            selected_index = self._team_unit_list.GetNextSelected(selected_index)
-        database.CONNECTION.commit()
-        reload_teams()
-        self._populate_team_unit_list(event=None)
-        self._populate_unit_list(event=None)
-        self.GetParent().panel_units.populate_unit_list(event=None)
+        sel_index = self._team_unit_list.GetFirstSelected()
+        while (sel_index != -1):
+            unit_id = self._team_unit_list.GetItem(sel_index, 2).getText()
+            ret = runeoptimizer.run(
+              ["team", "remove_unit", self._selected_team.id, unit_id]
+            )
+            sel_index = self._team_unit_list.GetNextSelected(sel_index)
+        data.reload_teams();
+        self._selected_team = data.TEAMS[self._selected_team.id]
+        self._populate_team_unit_list()
+        self._populate_unit_list()
+        self.GetParent().panel_units.populate_unit_list()
 
     def _team_selected(self, event=None):
         """Shows the team details.
@@ -393,30 +413,34 @@ class PanelTeams(wx.Panel):
         """
         selected_team_id = \
           str(self._team_list.GetItemData(self._team_list.GetFirstSelected()))
-        self._team_selected = data.TEAMS[selected_team_id]
+        self._selected_team = data.TEAMS[selected_team_id]
         self._populate_team_unit_list(event=None)
 
         # Unbind for the automatic change
         self._title_text.Unbind(wx.EVT_TEXT)
-        self._title_text.SetValue(self._team_selected.name)
+        self._title_text.SetValue(self._selected_team.name)
         # Rebind
         self._title_text.Bind(wx.EVT_TEXT, self._change_title);
 
         # Unbind for the automatic change
         self._priority_text.Unbind(wx.EVT_TEXT);
-        self._priority_text.SetValue(str(self._team_selected.priority))
+        self._priority_text.SetValue(str(self._selected_team.priority))
         # Rebind
         self._priority_text.Bind(wx.EVT_TEXT, self._change_priority);
 
         # Show details
-        self._details_sizer.ShowItems(True)
+        self._team_details_sizer.ShowItems(True)
+        self.Layout()
+
+        # Repopulate the all unit list to exlude units in selected team
+        self._populate_unit_list()
 
     def _change_title(self, event):
         """Prepares for a title change.
 
         Called everytime the selected team name is changed. It schedules a call
-        to self._save_new_title in two seconds, to give the user time to enter the
-        full title.
+        to self._save_new_title in two seconds, to give the user time to enter
+        the full title.
 
         Parameters
         ----------
@@ -426,14 +450,14 @@ class PanelTeams(wx.Panel):
         """
         self._title_change_pending = True
         self._title_timer = wx.Timer(self)
-        self._title_timer.Bind(wx.EVT_TIMER, self._save_new_title)
+        self.Bind(wx.EVT_TIMER, self._save_new_title)
         self._title_timer.StartOnce(2000)
 
     def _save_new_title(self, event=None):
         """Saves the team name to the database.
 
-        Called two seconds after the last change in self._title_text. If the name
-        is not valid (i.e. less than two characters), it doesn't apply the
+        Called two seconds after the last change in self._title_text. If the
+        nameis not valid (i.e. less than 4 characters), it doesn't apply the
         change and sets the font color to red to indicate error.
 
         Parameters
@@ -445,16 +469,15 @@ class PanelTeams(wx.Panel):
         if (self._title_change_pending):
             new_title = self._title_text.GetValue().replace("\n", "").strip()
             self._title_change_pending = False
-            if len(new_title.replace(" ", "")) > 2:
+            if len(new_title.replace(" ", "")) > 4:
                 self._title_text.SetStyle(
                   0, len(self._title_text.GetValue()),
                   wx.TextAttr(colText=wx.BLACK)
                 )
-                # TODO: Do this with a command
-                query = "UPDATE teams SET name = ? WHERE id = ?";
-                cursor = DB_CONNECTION.execute(query, (new_title, self._team_selected.id))
-                DB_CONNECTION.commit()
-                reload_teams()
+                ret = runeoptimizer.run(
+                  ["team", "update", self._selected_team.id, new_title]
+                )
+                data.reload_teams()
                 self._populate_team_list(event=None)
             else:
                 monospaceFont = wx.Font(
@@ -462,15 +485,16 @@ class PanelTeams(wx.Panel):
                   style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL
                 )
                 self._title_text.SetStyle(
-                  0, len(self._title_text.GetValue()), wx.TextAttr(colText=wx.RED)
+                  0, len(self._title_text.GetValue()),
+                  wx.TextAttr(colText=wx.RED)
                 )
 
     def _change_priority(self, event):
         """Prepares for a priority change.
 
         Called everytime the selected team priority is changed. It schedules a
-        call to self._save_new_priority in two seconds, to give the user time to
-        enter the full text.
+        call to self._save_new_priority in two seconds, to give the user time
+        to enter the full text.
 
         Parameters
         ----------
@@ -480,7 +504,7 @@ class PanelTeams(wx.Panel):
         """
         self._priority_change_pending = True
         self._priority_time = wx.Timer(self)
-        self._priority_time.Bind(wx.EVT_TIMER, self._save_new_priority)
+        self.Bind(wx.EVT_TIMER, self._save_new_priority)
         self._priority_time.StartOnce(2000)
 
     def _save_new_priority(self, event=None):
@@ -498,7 +522,9 @@ class PanelTeams(wx.Panel):
 
         """
         if (self._priority_change_pending):
-            new_priority = self._priority_text.GetValue().replace("\n", "").strip()
+            new_priority = (
+              self._priority_text.GetValue().replace("\n", "").strip()
+            )
             self._priority_change_pending = False
             if new_priority.isnumeric() and \
               int(new_priority) >= 0 and int(new_priority) <= 50:
@@ -506,11 +532,10 @@ class PanelTeams(wx.Panel):
                   0, len(self._priority_text.GetValue()),
                   wx.TextAttr(colText=wx.BLACK)
                 )
-                # TODO Do this with a command, when there is a command to do it.
-                query = "UPDATE teams SET priority = ? WHERE id = ?";
-                cursor = DB_CONNECTION.execute(query, (new_priority, self._team_selected.id))
-                DB_CONNECTION.commit()
-                reload_teams()
+                ret = runeoptimizer.run(
+                  ["team", "update", self._selected_team.id, new_priority]
+                )
+                data.reload_teams()
                 self._populate_team_list(event=None)
                 self.GetParent().panel_units.populate_unit_list(event=None)
             else:

+ 220 - 487
src/gui/runeoptimizer_gui/gui/PanelUnits.py

@@ -27,10 +27,9 @@ class PanelUnits(wx.Panel):
     -------
     populate_unit_list(event)
         Populates the unit list.
-
     """
 
-    _selected_unit = None
+    _sel_unit = None
     _unit_list = None
     _filter_name_text = None
     _filter_torage_check = None
@@ -57,7 +56,6 @@ class PanelUnits(wx.Panel):
             Panel parent.
         id : int, optional
             ID for the panel (default wx.ID_ANY).
-
         """
 
         # Parent constructor
@@ -65,475 +63,251 @@ class PanelUnits(wx.Panel):
 
         # Prepare some fonts
         title_font = wx.Font(
-          pointSize=16, family=wx.FONTFAMILY_DEFAULT,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
+          16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
         )
         subtitle_font = wx.Font(
-          pointSize=14, family=wx.FONTFAMILY_DEFAULT,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
+          12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
         )
         bold_font = wx.Font(
-          pointSize=10, family=wx.FONTFAMILY_DEFAULT,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
+          10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
         )
         monospace_font = wx.Font(
-          pointSize=10, family=wx.FONTFAMILY_TELETYPE,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL
+          10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
         )
         monospace_font_bold = wx.Font(
-          pointSize=10, family=wx.FONTFAMILY_TELETYPE,
-          style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
+          10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD
         )
         monospace_font_italic = wx.Font(
-          pointSize=10, family=wx.FONTFAMILY_TELETYPE,
-          style=wx.FONTSTYLE_ITALIC, weight=wx.FONTWEIGHT_NORMAL
-        )
+          10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL
+        )
+
+        # Sizer diagrams:
+        # panel_sizer (V)
+        #   unit_list_sizer (V)
+        #     unit_list_filter_sizer(V)
+        #   unit_details_sizer (V)
+        #     unit_info_sizer (H)
+        #       unit_info_text_sizer (V)
+        #     unit_runes_sizer (H)
+
+        # Create global sizer
+        panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        unit_list_sizer = wx.StaticBoxSizer(wx.VERTICAL, self, "Select unit:")
+        unit_list_filter_sizer = wx.StaticBoxSizer(
+          wx.VERTICAL, unit_list_sizer.GetStaticBox(), "Filers:"
+        )
+        self._unit_details_sizer = wx.BoxSizer(wx.VERTICAL)
+        unit_info_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        unit_info_text_sizer = wx.BoxSizer(wx.VERTICAL)
+        unit_runes_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        rune_sizer = wx.GridSizer(2, 3, (10, 10))
+
+        unit_runes_sizer.Add(rune_sizer, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.RIGHT, 10)
+        unit_info_sizer.Add(unit_info_text_sizer, 0)
+        self._unit_details_sizer.Add(unit_info_sizer, 0)
+        panel_sizer.Add(unit_list_sizer, 0, wx.EXPAND)
+        panel_sizer.Add(self._unit_details_sizer, 0, wx.LEFT, 20)
+        self.SetSizer(panel_sizer)
 
         # Unit selectable list
-        unit_list_box = wx.StaticBox(
-          self, label="Select unit:", id=wx.ID_ANY, pos=(0, 0), size=(210, 590)
-        )
-        wx.StaticText(
-          parent=unit_list_box, id=wx.ID_ANY,
-          label="Name             Prio.   Sto.",
-          pos=(10, 10), size=(190, 20)
-        ).SetFont(bold_font)
         self._unit_list = wx.ListCtrl(
-          parent=unit_list_box, id=wx.ID_ANY, pos=(10, 30), size=(190, 380),
-          style=wx.LC_REPORT|wx.LC_NO_HEADER
+          unit_list_sizer.GetStaticBox(), style=wx.LC_REPORT, size=(220, 0)
         )
-        self._unit_list.InsertColumn(0, "Name", width=120)
-        self._unit_list.InsertColumn(1, "Prio.", width=40)
+        unit_list_sizer.Add(self._unit_list, 1, wx.EXPAND)
+        self._unit_list.InsertColumn(0, "Name", width=110)
+        self._unit_list.InsertColumn(1, "Prio.", width=30)
         self._unit_list.InsertColumn(2, "Sto.", width=30)
+        self._unit_list.InsertColumn(3, "", width=0)
         self._unit_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self._unit_selected)
 
         # List filters
-        filterBox = wx.StaticBox(
-          parent=self, label="Filters:", id=wx.ID_ANY,
-          pos=(10, 430), size=(190, 150)
-        )
-        wx.StaticText(
-          parent=filterBox, label="Monster name", pos=(5, 5), size=(180, 20)
+
+        unit_list_filter_sizer.Add(
+          wx.StaticText(
+            unit_list_filter_sizer.GetStaticBox(), label="Monster name"
+          ), 0, wx.LEFT, 5
         )
         self._filter_name_text = wx.TextCtrl(
-          parent=filterBox, id=wx.ID_ANY, value="", pos=(5, 25),
-          size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
+          unit_list_filter_sizer.GetStaticBox()
+        )
+        unit_list_filter_sizer.Add(
+          self._filter_name_text, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5
         )
         self._filter_name_text.Bind(wx.EVT_TEXT, self.populate_unit_list)
         self._filter_storage_check = wx.CheckBox(
-          parent=filterBox, id=wx.ID_ANY,
-          label="Monsters in storage", pos=(5, 55), size=(180, 20)
+          unit_list_filter_sizer.GetStaticBox(), label="Monsters in storage"
         )
+        unit_list_filter_sizer.Add(self._filter_storage_check)
         self._filter_storage_check.SetValue(True)
         self._filter_storage_check.Bind(
           wx.EVT_CHECKBOX, self.populate_unit_list
         )
         self.filterNoRunesCheck = wx.CheckBox(
-          parent=filterBox, id=wx.ID_ANY,
-          label="Monsters without runes", pos=(5, 75), size=(180, 20)
+          unit_list_filter_sizer.GetStaticBox(), label="Monsters without runes"
         )
+        unit_list_filter_sizer.Add(self.filterNoRunesCheck)
         self.filterNoRunesCheck.SetValue(True)
         self.filterNoRunesCheck.Bind(
           wx.EVT_CHECKBOX, self.populate_unit_list
         )
         self._filter_no_teams_check = wx.CheckBox(
-          parent=filterBox, id=wx.ID_ANY,
-          label="Monsters not in teams", pos=(5, 95), size=(180, 20)
+          parent=unit_list_filter_sizer.GetStaticBox(),
+          label="Monsters not in teams"
         )
+        unit_list_filter_sizer.Add(self._filter_no_teams_check)
         self._filter_no_teams_check.SetValue(True)
         self._filter_no_teams_check.Bind(
           wx.EVT_CHECKBOX, self.populate_unit_list
         )
         self.populate_unit_list(None)
-
-        # Sizer for all the unit details. It will be hidden until a unit is
-        # selected.
-        self._details_sizer = wx.BoxSizer(wx.VERTICAL)
+        unit_list_sizer.Add(
+          unit_list_filter_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5
+        )
 
         # Unit name
-        self._name_label = wx.StaticText(
-          parent=self, label="", pos=(240, 0), size=(120, 30)
-        )
-        self._details_sizer.Add(self._name_label)
+        self._name_label = wx.StaticText(self, size=(170, 60))
         self._name_label.SetFont(title_font)
+        unit_info_text_sizer.Add(self._name_label, 0, wx.EXPAND)
 
         #Unit details
-        self._stars_label = wx.StaticText(
-          parent=self, label="", pos=(250, 30), size=(120, 100)
-        )
-        self._stars_label.SetFont(title_font)
-        self._details_sizer.Add(self._stars_label)
-        self._level_label = wx.StaticText(
-          parent=self, label="", pos=(250, 60), size=(120, 100)
-        )
-        self._details_sizer.Add(self._level_label)
+        self._stars_label = wx.StaticText(self)
+        self._stars_label.SetFont(subtitle_font)
+        unit_info_text_sizer.Add(self._stars_label, 0, wx.LEFT, 10)
+        self._level_label = wx.StaticText(self)
+        unit_info_text_sizer.Add(self._level_label, 0, wx.LEFT, 10)
         self._level_label.SetFont(subtitle_font)
-        self._priority_label = wx.StaticText(
-          parent=self, label="", pos=(250, 90), size=(120, 100)
-        )
+        self._priority_label = wx.StaticText(self)
         self._priority_label.SetFont(subtitle_font)
-        self._details_sizer.Add(self._priority_label)
-        self._id_label = wx.StaticText(
-          parent=self, label="", pos=(250, 120), size=(120, 100)
-        )
+        unit_info_text_sizer.Add(self._priority_label, 0, wx.LEFT, 10)
+        self._id_label = wx.StaticText(parent=self)
         self._id_label.SetFont(subtitle_font)
-        self._details_sizer.Add(self._id_label)
-
-        # Button to go to the ptimizer
-        optimize_button = wx.Button(
-          parent=self, id=wx.ID_ANY, pos=(700, 520), size=(120, 50),
-          style=wx.LC_REPORT, label="Optimize"
-        )
-        self._details_sizer.Add(optimize_button)
-        optimize_button.Bind(wx.EVT_BUTTON, self._go_to_optimizer)
+        unit_info_text_sizer.Add(self._id_label, 0, wx.LEFT, 10)
 
         # Stats table
-        self._stat_grid = wx.grid.Grid(
-          parent=self, id=wx.ID_ANY, pos=(520, 00), size=(195, 220)
-        )
-        self._details_sizer.Add(self._stat_grid)
-        self._stat_grid.CreateGrid(
-          numRows=10, numCols=2
-        )
+        self._stat_grid = wx.grid.Grid(self)
+        unit_info_sizer.Add(self._stat_grid, 0, wx.LEFT, 10)
+        self._stat_grid.CreateGrid(10, 2)
         self._stat_grid.EnableEditing(False)
         self._stat_grid.SetDefaultCellAlignment(
           horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE
         )
         self._stat_grid.SetDefaultCellFont(monospace_font)
-        self._stat_grid.SetColSize(col=0, width=80)
-        self._stat_grid.SetColLabelValue(col=0, value="Base")
-        self._stat_grid.SetColSize(col=1, width=80)
-        self._stat_grid.SetColLabelValue(col=1, value="Current")
-        self._stat_grid.SetRowLabelSize(width=35)
-        self._stat_grid.SetColLabelSize(height=20)
-        self._stat_grid.SetRowSize(row=0, height=20)
-        self._stat_grid.SetRowSize(row=1, height=20)
-        self._stat_grid.SetRowSize(row=2, height=20)
-        self._stat_grid.SetRowSize(row=3, height=20)
-        self._stat_grid.SetRowSize(row=4, height=20)
-        self._stat_grid.SetRowSize(row=5, height=20)
-        self._stat_grid.SetRowSize(row=6, height=20)
-        self._stat_grid.SetRowSize(row=7, height=20)
-        self._stat_grid.SetRowSize(row=8, height=20)
-        self._stat_grid.SetRowSize(row=9, height=20)
-        self._stat_grid.SetRowLabelValue(row=0, value=" HP")
-        self._stat_grid.SetRowLabelValue(row=1, value="ATK")
-        self._stat_grid.SetRowLabelValue(row=2, value="DEF")
-        self._stat_grid.SetRowLabelValue(row=3, value="SPD")
-        self._stat_grid.SetRowLabelValue(row=4, value="CRR")
-        self._stat_grid.SetRowLabelValue(row=5, value="CRD")
-        self._stat_grid.SetRowLabelValue(row=6, value="RES")
-        self._stat_grid.SetRowLabelValue(row=7, value="ACC")
-        self._stat_grid.SetRowLabelValue(row=8, value="EHP")
-        self._stat_grid.SetRowLabelValue(row=9, value="DMG")
+        self._stat_grid.SetColSize(0, 80)
+        self._stat_grid.SetColLabelValue(0, "Base")
+        self._stat_grid.SetColSize(1, 80)
+        self._stat_grid.SetColLabelValue(1, "Current")
+        self._stat_grid.SetRowLabelSize(35)
+        self._stat_grid.SetColLabelSize(20)
+        self._stat_grid.SetRowSize(0, 20)
+        self._stat_grid.SetRowSize(1, 20)
+        self._stat_grid.SetRowSize(2, 20)
+        self._stat_grid.SetRowSize(3, 20)
+        self._stat_grid.SetRowSize(4, 20)
+        self._stat_grid.SetRowSize(5, 20)
+        self._stat_grid.SetRowSize(6, 20)
+        self._stat_grid.SetRowSize(7, 20)
+        self._stat_grid.SetRowSize(8, 20)
+        self._stat_grid.SetRowSize(9, 20)
+        self._stat_grid.SetRowLabelValue(0, " HP")
+        self._stat_grid.SetRowLabelValue(1, "ATK")
+        self._stat_grid.SetRowLabelValue(2, "DEF")
+        self._stat_grid.SetRowLabelValue(3, "SPD")
+        self._stat_grid.SetRowLabelValue(4, "CRR")
+        self._stat_grid.SetRowLabelValue(5, "CRD")
+        self._stat_grid.SetRowLabelValue(6, "RES")
+        self._stat_grid.SetRowLabelValue(7, "ACC")
+        self._stat_grid.SetRowLabelValue(8, "EHP")
+        self._stat_grid.SetRowLabelValue(9, "DMG")
 
         # Team list
-        team_box = wx.StaticBox(
-          parent=self, label="Teams:",id=wx.ID_ANY,
-          pos=(730, 0), size=(160, 220)
-        )
-        self._details_sizer.Add(team_box)
+        team_box = wx.StaticBox(self, label="Teams:", size=(160, 220))
+        unit_info_sizer.Add(team_box, 0, wx.LEFT, 10)
         self._teams_label = wx.StaticText(
-          parent=team_box, label="", pos=(5, 5), size=(135, 190)
+          team_box, pos=(5, 15), size=(115, 190)
         )
         monospace_font.PointSize -= 2
         self._teams_label.SetFont(monospace_font)
         monospace_font.PointSize += 2
 
-        # Rune list
-        rune_box_list = [
-          wx.StaticBox(
-            parent=self, label="Slot1:",id=wx.ID_ANY,
-            pos=(365, 225), size=(130, 170)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot2:",id=wx.ID_ANY,
-            pos=(510, 225), size=(130, 170)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot3:",id=wx.ID_ANY,
-            pos=(510, 400), size=(130, 170)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot4:",id=wx.ID_ANY,
-            pos=(365, 400), size=(130, 170)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot5:",id=wx.ID_ANY,
-            pos=(220, 400), size=(130, 170)
-          ),
-          wx.StaticBox(
-            parent=self, label="Slot6:",id=wx.ID_ANY,
-            pos=(220, 225), size=(130, 170)
-          )
-        ]
-
-        self._set_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 0), size=(130, 10)
-          ),
-        ]
-
-        self._id_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 15), size=(130, 10)
-          )
-        ]
+        
 
 
-        self._main_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 35), size=(130, 10)
-          )
-        ]
-
-        self._innate_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 50), size=(130, 10)
-          )
-        ]
-
-
-        self._stat_label_list = [
-          [
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[0], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+        # Rune list
+        rune_box_list = []
+        for i in range(0, 6):
+            rune_box_list.append(
+              wx.StaticBoxSizer(wx.VERTICAL, self, "Slot" + str(i) + ":")
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[1], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+        rune_sizer.AddMany((
+          rune_box_list[5], rune_box_list[0], rune_box_list[1],
+          rune_box_list[4], rune_box_list[3], rune_box_list[2]
+        ))
+
+        self._set_label_list = []
+        self._id_label_list = []
+        self._main_label_list = []
+        self._innate_label_list = []
+        self._stat_label_list = [[], [], [], [], [], []]
+        self._eff_label_list = []
+        for i in range(0, 6):
+            self._set_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 13))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[2], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._set_label_list[i], 0, wx.LEFT, 3)
+            self._id_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox())
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[3], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._id_label_list[i], 0, wx.LEFT, 5)
+            rune_box_list[i].Add(
+              wx.StaticLine(rune_box_list[i].GetStaticBox(), size=(130, 1)), 0
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[4], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            self._main_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 13))
             )
-          ],
-          [
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 70), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 85), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 100), size=(130, 10)
-            ),
-            wx.StaticText(
-              parent=rune_box_list[5], id=wx.ID_ANY, label="",
-              pos=(5, 115), size=(130, 10)
+            rune_box_list[i].Add(self._main_label_list[i], 0, wx.LEFT, 3)
+            self._innate_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 13))
             )
-          ]
-        ]
-        self._eff_label_list = [
-          wx.StaticText(
-            parent=rune_box_list[0], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[1], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[2], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[3], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[4], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-          wx.StaticText(
-            parent=rune_box_list[5], id=wx.ID_ANY, label="",
-            pos=(5, 135), size=(130, 10)
-          ),
-        ]
-        for i in range(0, 6):
-            # Separators
-            wx.StaticLine(
-              parent=rune_box_list[i], id=wx.ID_ANY,
-              pos=(0, 30), size=(130, 1), style=wx.LC_REPORT
+            rune_box_list[i].Add(
+              self._innate_label_list[i], 0, wx.LEFT|wx.BOTTOM, 3
+            )
+            for j in range(0, 4):
+                self._stat_label_list[i].append(wx.StaticText(
+                  rune_box_list[i].GetStaticBox(), size=(130, 13)
+                ))
+                rune_box_list[i].Add(
+                  self._stat_label_list[i][j], 0, wx.LEFT, 3
+                )
+            rune_box_list[i].Add(
+              wx.StaticLine(rune_box_list[i].GetStaticBox(), size=(130, 1)), 0
+            )
+            self._eff_label_list.append(
+              wx.StaticText(rune_box_list[i].GetStaticBox(), size=(130, 13))
             )
-            wx.StaticLine(
-              parent=rune_box_list[i], id=wx.ID_ANY,
-              pos=(0, 135), size=(130, 1), style=wx.LC_REPORT
+            rune_box_list[i].Add(
+              self._eff_label_list[i], 0, wx.LEFT|wx.BOTTOM, 3
             )
+
             # Set fonts
-            rune_box_list[i].SetFont(monospace_font)
+            rune_box_list[i].GetStaticBox().SetFont(monospace_font)
             self._eff_label_list[i].SetFont(monospace_font_italic)
             self._main_label_list[i].SetFont(monospace_font_bold)
             self._innate_label_list[i].SetFont(monospace_font_italic)
-            # Add boxes to sizer
-            self._details_sizer.Add(rune_box_list[i])
+
+        # Button to go to the ptimizer
+        #button_sizer = wx.BoxSizer(wx.VERTICAL)
+        optimize_button = wx.Button(self, size=(120, 50), label="Optimize")
+        #button_sizer.Add(0, 200, 1)
+        #button_sizer.Add(optimize_button, wx.ALIGN_BOTTOM|wx.RIGHT, 30)
+        #button_sizer.Add(0, 200, 1)
+        unit_runes_sizer.Add(optimize_button, 0, wx.ALIGN_BOTTOM|wx.BOTTOM, 30)
+        #unit_runes_sizer.Add(10, 0, 1)
+        optimize_button.Bind(wx.EVT_BUTTON, self._go_to_optimizer)
+        self._unit_details_sizer.Add(unit_runes_sizer, wx.EXPAND)
 
         # By default, hide all optimization options
-        self._details_sizer.ShowItems(False)
+        self._unit_details_sizer.ShowItems(False)
+        self.Layout()
 
 
     def populate_unit_list(self, event=None):
@@ -571,9 +345,7 @@ class PanelUnits(wx.Panel):
                 self._unit_list.SetItem(i, 2, "X")
             else:
                 self._unit_list.SetItem(i, 2, " ")
-            # TODO Items can't hold too much data in Windows, store the first
-            # nine digits and rerieve it from database with LIKE
-            self._unit_list.SetItemData(i, int(unit.id))
+            self._unit_list.SetItem(i, 3, unit.id)
             i = i + 1
 
     def _go_to_optimizer(self, event = None):
@@ -586,7 +358,7 @@ class PanelUnits(wx.Panel):
 
         """
         self.GetParent().panel_optimizer.select_unit(
-          event=None, unit_id=self._selected_unit.id
+          None, self._sel_unit.id
         )
         self.GetParent().ChangeSelection(2)
 
@@ -601,134 +373,95 @@ class PanelUnits(wx.Panel):
             The event that triggered the call (default is None).
 
         """
-        unit_id = \
-          str(self._unit_list.GetItemData(self._unit_list.GetFirstSelected()))
 
-        self._selected_unit = data.UNITS[unit_id]
+        unit_id = self._unit_list.GetItem(
+          self._unit_list.GetFirstSelected(), 3
+        ).GetText()
+
+        self._sel_unit = data.UNITS[unit_id]
 
         # Set label
-        self._name_label.SetLabel(self._selected_unit.name)
+        self._name_label.SetLabel(self._sel_unit.name)
         stars_label_text = ""
-        for i in range(0, self._selected_unit.stars):
+        for i in range(0, self._sel_unit.stars):
             stars_label_text += "\u272D"
         self._stars_label.SetLabel(stars_label_text)
-        self._level_label.SetLabel("Lv. " + str(self._selected_unit.level))
+        self._level_label.SetLabel("Lv. " + str(self._sel_unit.level))
         self._priority_label.SetLabel(
-          "Priority: " + str(self._selected_unit.priority)
+          "Priority: " + str(self._sel_unit.priority)
         )
-        self._id_label.SetLabel("#" + self._selected_unit.id)
-        self._details_sizer.ShowItems(True)
+        self._id_label.SetLabel("#" + self._sel_unit.id)
+        self._unit_details_sizer.ShowItems(True)
+        self.Layout()
+        
 
         # Populate stats
-        self._stat_grid.SetCellValue(
-          row=0, col=0, s=str(self._selected_unit.base_stats.hp)
-        )
-        self._stat_grid.SetCellValue(
-          row=0, col=1, s=str(self._selected_unit.stats.hp)
-        )
-        self._stat_grid.SetCellValue(
-          row=1, col=0, s=str(self._selected_unit.base_stats.atk)
-        )
-        self._stat_grid.SetCellValue(
-          row=1, col=1, s=str(self._selected_unit.stats.atk)
-        )
-        self._stat_grid.SetCellValue(
-          row=2, col=0, s=str(self._selected_unit.base_stats.dfc)
-        )
-        self._stat_grid.SetCellValue(
-          row=2, col=1, s=str(self._selected_unit.stats.dfc)
-        )
-        self._stat_grid.SetCellValue(
-          row=3, col=0, s=str(self._selected_unit.base_stats.spd)
-        )
-        self._stat_grid.SetCellValue(
-          row=3, col=1, s=str(self._selected_unit.stats.spd)
-        )
-        self._stat_grid.SetCellValue(
-          row=4, col=0, s=str(self._selected_unit.base_stats.crr)
-        )
-        self._stat_grid.SetCellValue(
-          row=4, col=1, s=str(self._selected_unit.stats.crr)
-        )
-        self._stat_grid.SetCellValue(
-          row=5, col=0, s=str(self._selected_unit.base_stats.crd)
-        )
-        self._stat_grid.SetCellValue(
-          row=5, col=1, s=str(self._selected_unit.stats.crd)
-        )
-        self._stat_grid.SetCellValue(
-          row=6, col=0, s=str(self._selected_unit.base_stats.res)
-        )
-        self._stat_grid.SetCellValue(
-          row=6, col=1, s=str(self._selected_unit.stats.res)
-        )
-        self._stat_grid.SetCellValue(
-          row=7, col=0, s=str(self._selected_unit.base_stats.acc)
-        )
-        self._stat_grid.SetCellValue(
-          row=7, col=1, s=str(self._selected_unit.stats.acc)
-        )
-        self._stat_grid.SetCellValue(
-          row=8, col=0, s=str(self._selected_unit.base_stats.ehp)
-        )
-        self._stat_grid.SetCellValue(
-          row=8, col=1, s=str(self._selected_unit.stats.ehp)
-        )
-        self._stat_grid.SetCellValue(
-          row=9, col=0, s=str(self._selected_unit.base_stats.dmg)
-        )
-        self._stat_grid.SetCellValue(
-          row=9, col=1, s=str(self._selected_unit.stats.dmg)
-        )
+        base_stats = self._sel_unit.base_stats.values()
+        curr_stats = self._sel_unit.stats.values()
+        for i in range(0, 10):
+            b_stat = str(base_stats[i])
+            c_stat = str(base_stats[i])
+            if i in [4, 5, 6, 7]:
+                b_stat += "%"
+                c_stat += "%"
+            else:
+                b_stat += " "
+                c_stat += " "
+            self._stat_grid.SetCellValue(i, 0, b_stat)
+            self._stat_grid.SetCellValue(i, 1, c_stat)
 
         # Write the team list
         team_label_text = ""
-        for team in self._selected_unit.teams:
-            team_label_text += "-" + team.name[0:16].ljust(16, " ")
+        for team in self._sel_unit.teams:
+            team_label_text += "-" + team.name[0:14].ljust(14, " ")
             team_label_text += ("(#" + team.id + ")").rjust(6) + "\n"
         self._teams_label.SetLabel(team_label_text)
 
+        # Clear the rune labels
+        for i in range(0, 6):
+            self._set_label_list[i].SetLabel("")
+            self._id_label_list[i].SetLabel("")
+            self._eff_label_list[i].SetLabel("")
+            self._main_label_list[i].SetLabel("")
+            self._innate_label_list[i].SetLabel("")
+            for j in range(0, 4):
+                self._stat_label_list[i][j].SetLabel("")
+
         # Populate the runes
         i = 0
-        for rune in  self._selected_unit.runes:
+        for rune in self._sel_unit.runes:
+            if rune is None:
+                continue
             # Rows are 18 charactes width
 
             # First line: Set name, level
             self._set_label_list[rune.slot - 1].SetLabel(
-              rune.type_name[0:9].ljust(12, " ") + \
-              "+" + str(rune.level).ljust(3, " ")
+              rune.type_name[0:9].ljust(12, " ")
+              + "+" + str(rune.level).ljust(3, " ")
             )
 
             # Second line: ID
-            lvl_set_eff = rune.type_name[0:6].ljust(6, " ")
-            effv = str(("{:.2f}".format(rune.efficiency)).rjust(5, " "))
-            eff = ("  Eff:" + str(effv) + "%")
-            lvl_set_eff += eff
             self._id_label_list[i].SetLabel(("#" + rune.id).rjust(15, " "))
 
-            # Efficiency
-            label_eff_text = \
-              "Eff.: " + ("{:.1f}".format(rune.efficiency)).rjust(4, " ") + \
-              "/" + ("{:.1f}".format(rune.max_efficiency)).rjust(4, " ")
+            # Last line: Efficiencies
+            label_eff_text = (
+              "Eff.: " + ("{:.1f}".format(rune.efficiency)).rjust(4, " ")
+              + "/" + ("{:.1f}".format(rune.max_efficiency)).rjust(4, " ")
+            )
             self._eff_label_list[rune.slot - 1].SetLabel(label_eff_text)
 
             # Write all stats
-            innate_label = "          "
             for stat in rune.stats:
                 if stat == None:
                     continue
                 slot = stat.slot
                 if stat.is_enchanted:
                     name = (
-                      stat.name
-                      .replace("%", "").
-                      replace(" ", "") + "* "
+                      stat.name.replace("%", "").replace(" ", "") + "* "
                     ).rjust(5, " ")
                 else:
                     name = (
-                      stat.name
-                      .replace("%", "")
-                      .replace(" ", "") + "  "
+                      stat.name.replace("%", "").replace(" ", "") + "  "
                     ).rjust(5, " ")
                 value = str(stat.value)
                 if stat.stat in [2, 4, 6, 9, 10, 11, 23]:
@@ -744,8 +477,8 @@ class PanelUnits(wx.Panel):
                 if slot == -1: # main
                     self._main_label_list[i].SetLabel(line)
                 elif slot == 0: # innate
-                    innate_label = line
+                     self._innate_label_list[i].SetLabel(line)
                 else: # normal stats
                     self._stat_label_list[i][slot - 1].SetLabel(line)
-            self._innate_label_list[i].SetLabel(innate_label)
             i = i + 1
+        self.Layout()

+ 3 - 3
src/gui/runeoptimizer_gui/gui/RuneOptimizerFrame.py

@@ -37,8 +37,7 @@ class RuneOptimizerFrame(wx.Frame):
 
         super(RuneOptimizerFrame, self).__init__(*args, **kw)
 
-        # Create and configure the menue
-        pnl = wx.Panel(self)
+        # Create and configure the menu
         self._make_menu_bar()
 
         # Create the statsusbar
@@ -67,7 +66,8 @@ class RuneOptimizerFrame(wx.Frame):
         self.SetStatusText(status)
 
         # Create tabs
-        tabs = Tab_List(parent=pnl, id=wx.ID_ANY)
+        tabs = Tab_List(parent=self, id=wx.ID_ANY)
+        self.Layout()
 
     def _make_menu_bar(self=None):
         """

+ 24 - 8
src/gui/runeoptimizer_gui/gui/TabList.py

@@ -89,7 +89,7 @@ class Tab_List(wx.Listbook):
 
         # Parent constructor
         wx.Listbook.__init__(
-          self, parent, id=id, pos=(0, 0), size=(1000, 650), style=wx.BK_LEFT
+          self, parent, id=id, style=wx.BK_LEFT
         )
 
         # Load the icons
@@ -136,7 +136,7 @@ class Tab_List(wx.Listbook):
         """
         Things to do once the tab has finished changing.
 
-        Currently, it does nothing.
+        It forces a layout refresh of the selected layout.
 
         Parameters
         ----------
@@ -145,10 +145,27 @@ class Tab_List(wx.Listbook):
 
         """
 
-        #old = event.GetOldSelection()
-        #new = event.GetSelection()
-        #sel = self.GetSelection()
-        event.Skip()
+        old = event.GetOldSelection()
+        new = event.GetSelection()
+        sel = self.GetSelection()
+        #self._timer = wx.Timer(self)
+        #self.Bind(wx.EVT_TIMER, self._redraw_layout)
+        #self._timer.StartOnce(2000)
+        self._redraw_layout()
+
+    def _redraw_layout(self, event=None):
+        if self.GetSelection() == 0:
+            self._panel_units.Layout()
+        elif self.GetSelection() == 1:
+            self._panel_teams.Layout()
+        elif self.GetSelection() == 2:
+            self._panel_optimizer.Layout()
+        elif self.GetSelection() == 3:
+            self._panel_results.Layout()
+        elif self.GetSelection() == 4:
+            self._panel_info.Layout()
+        self.Layout()
+        #event.Skip()
 
     def _on_page_changing(self, event):
         """
@@ -160,10 +177,9 @@ class Tab_List(wx.Listbook):
         ----------
         event : wx.Event, optional
             The event that triggered the call (default is None).
-
         """
 
         #old = event.GetOldSelection()
         #new = event.GetSelection()
         #sel = self.GetSelection()
-        event.Skip()
+        #event.Skip()

+ 0 - 0
src/gui/runeoptimizer_gui/runeoptimizer/__init__.py


+ 39 - 0
src/gui/runeoptimizer_gui/runeoptimizer/runeoptimizer.py

@@ -0,0 +1,39 @@
+"""
+This file is part of RuneOptimizer.
+
+RuneOptimizer 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.
+
+RuneOptimizer 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
+RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+
+"""
+import subprocess
+
+def run(args):
+    """
+    Executes a runeoptimizer with the arguments.
+    
+    Can be used for short execution time commands (team management,
+    quick commands...). For long processes (update, optimization...) an
+    it's better to not use this function. Returns the result code, and
+    every output to stdout and stderris discarded
+    """
+    command = []
+    command.append("runeoptimizer")
+    command_str = "runeoptimizer"
+    for arg in args:
+        command.append(arg)
+        command_str += " " + arg
+    process = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+    stdout, stderr = process.communicate()
+    exit_code = process.wait()
+    print(command_str + "    (" + str(exit_code) + ")")
+    return exit_code

+ 12 - 35
src/gui/runeoptimizer_gui/runeoptimizer_gui.py

@@ -19,58 +19,30 @@ RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
 """
 
 import sqlite3
-import subprocess
 import json
 import os
 import signal
 import pipes
 import appdirs
+import subprocess
 from types import SimpleNamespace
 from time import sleep
 import wx
 import wx.grid
 import wx.adv
 import data.data as data
+import runeoptimizer.runeoptimizer as runeoptimizer
 import values.values as values
 import database.database as database
-from entity.Team import Team
-from entity.Unit import Unit
 from gui.RuneOptimizerFrame import RuneOptimizerFrame
 
 # The path to the RuneOptimizer executable. Different in Windows.
 executable_path = \
-  os.path.dirname(os.path.realpath(__file__)) + '/../RuneOptimizer'
+  os.path.dirname(os.path.realpath(__file__)) + '/../runeoptimizer'
 if os.name == 'nt':
     executable_path = \
-      os.path.dirname(os.path.realpath(__file__)) + '\\..\\RuneOptimizer.exe'
+      os.path.dirname(os.path.realpath(__file__)) + '\\..\\runeoptimizer.exe'
 
-def reload_units():
-    # Get all the units, sorted by priority desc
-    data.UNITS = {}
-    cursor = database.CONNECTION.execute("""
-      SELECT
-        id,
-        (
-          SELECT sum(priority)
-          FROM teams
-          WHERE id IN (SELECT team FROM units_teams WHERE unit = units.id)
-        ) AS prio
-      FROM units
-      ORDER BY prio DESC;
-    """)
-    rows = cursor.fetchall()
-    for row in rows:
-        data.UNITS[str(row[0])] = Unit(str(row[0]))
-
-def reload_teams():
-    # Get all the teams, sorted by priority desc
-    data.TEAMS = {}
-    cursor = database.CONNECTION.execute(
-      "SELECT id FROM teams ORDER BY priority DESC;"
-    )
-    rows = cursor.fetchall()
-    for row in rows:
-        data.TEAMS[str(row[0])] = Team(str(row[0]))
 
 if __name__ == '__main__':
     """
@@ -90,12 +62,17 @@ if __name__ == '__main__':
     # TODO: Test for database to exist
 
     # Open the database
+    print(appdirs.user_data_dir("RuneOptimizer", "", roaming=True))
     database.CONNECTION = sqlite3.connect(
-      appdirs.user_data_dir("RuneOptimizer") + "/data.sqlite"
+      appdirs.user_data_dir("RuneOptimizer", "", roaming=True) + "/data.sqlite"
     )
+    
+    # This makes sure that the executable has connection to the database.
+    runeoptimizer.run(["gui"])
 
-    reload_units()
-    reload_teams()
+    data.reload_units()
+    data.reload_teams()
+    
 
     # Start the GUI
     app = wx.App()

Vissa filer visades inte eftersom för många filer har ändrats