diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f76912e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.iml +*Launch4j_Config.xml diff --git a/Coin Collection.pdf b/Coin Collection.pdf deleted file mode 100644 index 6dc22ea..0000000 Binary files a/Coin Collection.pdf and /dev/null differ diff --git a/CoinCollection.jar b/CoinCollection.jar deleted file mode 100644 index f2ebd68..0000000 Binary files a/CoinCollection.jar and /dev/null differ diff --git a/NumismatistAPI/src/DatabaseConnection.kt b/NumismatistAPI/src/DatabaseConnection.kt new file mode 100644 index 0000000..000cd78 --- /dev/null +++ b/NumismatistAPI/src/DatabaseConnection.kt @@ -0,0 +1,179 @@ +import java.sql.* + +class DatabaseConnection() { + + companion object { + const val DEFAULT_DATABASE_SERVER = "localhost" + const val DEFAULT_DATABASE_NAME = "CoinCollection" + const val DEFAULT_DATABASE_USERNAME = "coins" + const val DEFAULT_DATABASE_PASSWORD = "coinDatabasePassword1!" + const val DEFAULT_PORT_NUMBER = 3306 + + const val DEFAULT_TIMEOUT_SECONDS = 30 + } + + var statement: Statement? = null + private lateinit var connection : Connection + + private var username = DEFAULT_DATABASE_USERNAME + private var password = DEFAULT_DATABASE_PASSWORD + private var databaseName = DEFAULT_DATABASE_NAME + private var databaseServer = DEFAULT_DATABASE_SERVER + private var port = DEFAULT_PORT_NUMBER + var timeout = DEFAULT_TIMEOUT_SECONDS + + /** + * @throws SQLException if connection to SQL Database fails + * @throws ClassNotFoundException if jar file not loaded + */ + @Throws(ClassNotFoundException::class, SQLException::class) + constructor(username: String = DEFAULT_DATABASE_USERNAME, + password: String = DEFAULT_DATABASE_PASSWORD, + databaseName: String = DEFAULT_DATABASE_NAME, + databaseServer: String = DEFAULT_DATABASE_SERVER, + port: String = "" + DEFAULT_PORT_NUMBER) : this() { + + this.username = username + this.password = password + this.databaseServer = databaseServer + this.databaseName = databaseName + this.port = Integer.parseInt(port) + + try { + connect(username, password, databaseName, databaseServer, port) + disconnect() + } + catch (e: ClassNotFoundException) { + throw e + } + catch (e: SQLException) { + throw e + } + } + + /** + * @param username: Username for database access + * @param password: Password for database access + * @param databaseName: Name of database to be loaded. Defaults to "CoinProgram" + * @param databaseLocation: Name or IP address of the SQL server hosting the database. Defaults to "localhost" + * @param port: IP port to use while connecting + * + * @throws SQLException if connection fails + * + * Connects to a database and stores the statement for later use. + */ + @Throws(ClassNotFoundException::class, SQLException::class) + fun connect(username: String = DEFAULT_DATABASE_USERNAME, + password: String = DEFAULT_DATABASE_PASSWORD, + databaseName: String = DEFAULT_DATABASE_USERNAME, + databaseLocation: String = DEFAULT_DATABASE_SERVER, + port: Int = DEFAULT_PORT_NUMBER) { + connect(username, password, databaseName, databaseLocation, "" + port) + } + + /** + * @param username: Username for database access + * @param password: Password for database access + * @param databaseName: Name of database to be loaded. Defaults to "CoinProgram" + * @param databaseLocation: Name or IP address of the SQL server hosting the database. Defaults to "localhost" + * @param port: IP port to use while connecting + * + * @throws SQLException if connection fails + * + * Connects to a database and stores the statement for later use. + */ + @Throws(ClassNotFoundException::class, SQLException::class) + fun connect(username: String = DEFAULT_DATABASE_USERNAME, + password: String = DEFAULT_DATABASE_PASSWORD, + databaseName: String = DEFAULT_DATABASE_USERNAME, + databaseLocation: String = DEFAULT_DATABASE_SERVER, + port: String = "" + DEFAULT_PORT_NUMBER) { + + val url = "jdbc:mysql://$databaseLocation:$port/$databaseName" + + // Set timeout length. Unlimited if this is not called + DriverManager.setLoginTimeout(timeout) + + // Try to connect to database + connection = try { + // Load the SQL driver + Class.forName("com.mysql.jdbc.Driver") + + DriverManager.getConnection( + url, username, password + ) + }catch (cnf : ClassNotFoundException) { + throw cnf + } + catch (e: SQLException) { + throw e + } + + statement = connection.createStatement() + } + + /** + * Disconnects from the database + */ + fun disconnect() { + try { + statement?.close() + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + } + + /** + * @param query SQL query to execute + * @return Results from SQL query + * + * Runs a SQL Query and returns the results. If there was an error, returns null. + */ + fun runQuery(query: String) : ResultSet? { + + return try { + connect(username, password, databaseName, databaseServer, port) + statement?.executeQuery(query) + } + catch (e: SQLException) { + e.printStackTrace() + null + } + } + + /** + * @param sql SQL command to run + * @return Number of rows affected + * + * Runs a command like INSERT, UPDATE or DELETE and returns the affected number of rows + * Returns -1 if there was an error + */ + @Throws(SQLException::class) + fun runUpdate(sql: String) : Int { + return try { + connect(username, password, databaseName, databaseServer, port) + val changes = statement?.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS) + + return changes ?: -1 + } + catch (e: SQLException) { + e.printStackTrace() + -1 + } + } + + /** + * Returns status of an update or insert. Implies that only 1 row should be updated at a time. + * + * @param rows How many rows were modified by the insert or update + * @return A message about whether the command was successful + */ + fun wasSuccessful(rows: Int): String { + return when(rows) { + 1 -> NumismatistAPI.getString("db_message_success")!! + 0 -> NumismatistAPI.getString("db_message_noChange")!! + -1 -> NumismatistAPI.getString("db_message_error")!! + else -> NumismatistAPI.getString("db_message_multipleRows")!! + } + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/META-INF/MANIFEST.MF b/NumismatistAPI/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..2ea95db --- /dev/null +++ b/NumismatistAPI/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Implementation-Title: Numismatist API +Implementation-Version: 0.1 diff --git a/NumismatistAPI/src/NumismatistAPI.kt b/NumismatistAPI/src/NumismatistAPI.kt new file mode 100644 index 0000000..67d9fa6 --- /dev/null +++ b/NumismatistAPI/src/NumismatistAPI.kt @@ -0,0 +1,1506 @@ +import items.* +import items.Currency +import items.Set +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import java.io.* +import java.net.URLDecoder +import java.nio.charset.StandardCharsets +import java.sql.ResultSet +import java.sql.SQLException +import java.util.* +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory +import kotlin.collections.ArrayList + +@Suppress("unused") +class NumismatistAPI { + + private var connection = DatabaseConnection() + + private var databaseServer = DatabaseConnection.DEFAULT_DATABASE_SERVER + private var databaseName = DatabaseConnection.DEFAULT_DATABASE_NAME + private var databasePort = DatabaseConnection.DEFAULT_PORT_NUMBER + private var databaseUserName = DatabaseConnection.DEFAULT_DATABASE_USERNAME + private var databasePassword = DatabaseConnection.DEFAULT_DATABASE_PASSWORD + + var connectionTimeout = DatabaseConnection.DEFAULT_TIMEOUT_SECONDS + set(value) { + field = value + connection.timeout = value + } + + var imagePath = "" + + // The below objects act like a local cache of the database + /** + * A list of coins that are not in a set or book + */ + private var coins = ArrayList() + //A list of coins that are in a set. Should be empty after all database items are filled. + private var coinsInSets = ArrayList() + //A list of coins that are in a book. Should be empty after all database items are filled. + private var coinsInBooks = ArrayList() + private var bills = ArrayList() + private var billsInSets = ArrayList() + private var sets = ArrayList() + private var setsInSets = ArrayList() + private var books = ArrayList() + private var containers = ArrayList() + private var countries = ArrayList() + private var currencies = ArrayList() + + var topOfCountriesList = ArrayList() + set(value) { + field = value + countries = Country.sort(countries, topOfCountriesList) + countryListener?.countryListChanged(countries) + } + + var coinListener : CoinListener? = null + var billListener : BillListener? = null + var setListener : SetListener? = null + var bookListener : BookListener? = null + var countryListener : CountryListener? = null + + interface CoinListener { + /** + * @param coins A list of coins retrieved from the database + */ + fun coinListRetrievedFromFb(coins :ArrayList) { + } + } + + interface BillListener { + /** + * @param bills A list of bills (banknotes) retrieved from the database + */ + fun billListRetrievedFromFb(bills :ArrayList) { + } + } + + interface SetListener { + /** + * @param sets A list of sets retrieved from the database + */ + fun setListRetrievedFromFb(sets :ArrayList) { + } + } + + interface BookListener { + /** + * @param books A list of books retrieved from the database + */ + fun bookListRetrievedFromFb(books :ArrayList) { + } + } + + interface CountryListener { + /** + * @param countries A list of countries + */ + fun countryListChanged(countries :ArrayList) {} + } + + companion object { + + fun getString(stringName: String): String? { + return getString(stringName, Locale.getDefault()) + } + + fun getString(stringName: String, locale: Locale): String? { + return ResourceBundle.getBundle("res.apiStrings", locale).getString(stringName) + } + + /** + * Gets the full path of the res folder. Takes into effect whether the program is running from a .jar file + * + * @param path The base folder to look in. Such as XML + * @param subPath Sub-folders to look in, if any + * + * @return The full path of the specific resource folder you are looking for + */ + fun getResPath(path: String, vararg subPath: String): String { + + var newPath = path + // Becomes / for *nix systems, and \ for Windows + val separator = System.getProperty("file.separator") + newPath = "res/$newPath" + + // If running from a JAR file + return if (!NumismatistAPI::class.java.getResource("NumismatistAPI.class")!!.toString().startsWith("file:")) { + val finalPath = StringBuilder(newPath) + finalPath.append("/") + for (addPath in subPath) { + finalPath.append(addPath) + finalPath.append("/") + } + Thread.currentThread().contextClassLoader.getResource(finalPath.toString())!!.toString() + } else { + var resPath = "" + try { + val classLoader = javaClass.classLoader + + // String returned is URL, so it's HTML encoded. Must decode first + val decodedPath = URLDecoder.decode(classLoader.getResource(newPath)?.file, StandardCharsets.UTF_8) + val file = File(decodedPath) + resPath = file.absolutePath + } catch (e: NullPointerException) { + e.printStackTrace() + } + // set path based on OS - slashes change based on OS + val finalPath = StringBuilder(resPath) + finalPath.append(separator) + for (addPath in subPath) { + finalPath.append(addPath) + finalPath.append(separator) + } + finalPath.toString() + } + } + + /** + * Gets a file from the resources folder + * + * @param path which resource folder to get the file from + * @return If the path is not blank, and path contains a file, returns the requested file. Otherwise, returns null + */ + fun getFileFromRes(path: String) : File? { + return if(path.isBlank()) { + println("path is blank") + null + } + else { + // If we're running a jar file + if(path.startsWith("jar")) { + val pathSub = path.substring(path.lastIndexOf("!") + 2) + streamToFile(NumismatistAPI::class.java.classLoader.getResourceAsStream(pathSub)) + } + else { + File(path) + } + } + } + + /** + * Converts an InputStream to a File + * + * @param in InputStream to convert + * @return Resulting file + */ + private fun streamToFile(`in`: InputStream?): File? { + return if (`in` == null) { + null + } else try { + val f = File.createTempFile(`in`.hashCode().toString(), ".tmp") + f.deleteOnExit() + val out = FileOutputStream(f) + val buffer = ByteArray(1024) + var bytesRead: Int + while (`in`.read(buffer).also { bytesRead = it } != -1) { + out.write(buffer, 0, bytesRead) + } + f + } catch (e: IOException) { + null + } + } + + /** + * Copies source file to destination file. + * + * @param src The source file to be copied + * @param dest Path of the destination of the copied file + * + * @return True if file is successfully copied. False if source file doesn't exist or if copy fails. + */ + @Throws(IOException::class) + fun copyFile(src: File, dest: String): Boolean { + return copyFile(src, File(dest)) + } + + /** + * Copies source file to destination file. + * + * @param src Path of the source file to be copied + * @param dest The destination of the copied file + * + * @return True if file is successfully copied. False if source file doesn't exist or if copy fails. + */ + @Throws(IOException::class) + fun copyFile(src: String, dest: File): Boolean { + return copyFile(File(src), dest) + } + + /** + * Copies source file to destination file. + * + * @param src Path of the source file to be copied + * @param dest Path of the destination of the copied file + * + * @return True if file is successfully copied. False if source file doesn't exist or if copy fails. + */ + @Throws(IOException::class) + fun copyFile(src: String, dest: String): Boolean { + return copyFile(File(src), File(dest)) + } + + /** + * Copies source file to destination file. + * + * @param src The source file to be copied + * @param dest The destination of the copied file + * + * @return True if file is successfully copied. False if source file doesn't exist or if copy fails. + */ + @Throws(IOException::class) + fun copyFile(src: File, dest: File): Boolean { + if (!src.exists()) return false + + // Create directories and file if it doesn't exist, if file already exists will do nothing + try { + if (dest.isFile) { + dest.parentFile.mkdirs() + dest.createNewFile() + } + } catch (e: IOException) { + throw e + } + + // Copy the file + try { + FileInputStream(src).use { `is` -> + FileOutputStream(dest).use { os -> + // buffer size 1K + val buf = ByteArray(1024) + var bytesRead: Int + while (`is`.read(buf).also { bytesRead = it } > 0) { + os.write(buf, 0, bytesRead) + } + } + } + } catch (e: java.lang.Exception) { + return false + } + return true + } + } + + /** + * Does an initial query of the database to get all relevant information and cache it in objects. This should + * be run in a background process + */ + private fun getAllInfoFromDB() { + + currencies = ArrayList() + countries = ArrayList() + containers = ArrayList() + coins = ArrayList() + bills = ArrayList() + sets = ArrayList() + books = ArrayList() + + getCurrenciesFromDb() + getCountriesFromDb() + + getContainersFromDb() + getCoinsFromDb() + getBillsFromDb() + getSetsFromDb() + // Needs to be last, otherwise this could take much longer + getBooksFromDb() + } + + /** + * Sets the connection information for the database, connects to the database, and then calls getAllInfoFromDB + * to cache the initial queries. This should be called from a background process. + * + * @see getAllInfoFromDB + * + * @param server Name or IP address of the database server + * @param dbName Name of the database to connect to on the server + * @param port Port number to use for the connection + * @param username Username to use for the database connection + * @param password Password to use for the database connection + */ + @Throws(ClassNotFoundException::class, SQLException::class) + fun setDbInfo(server : String, dbName: String, port: Int, username: String, password : String) { + databaseServer = server + databaseName = dbName + databasePort = port + databaseUserName = username + databasePassword = password + + connection = DatabaseConnection(databaseUserName, databasePassword, databaseName, databaseServer, "" + databasePort) + + connection.connect(databaseUserName, databasePassword, databaseName, databaseServer, databasePort) + getAllInfoFromDB() + } + + /** + * Sets the connection information for the database, connects to the database, and then calls getAllInfoFromDB + * to cache the initial queries. This should be called from a background process. + * + * @see getAllInfoFromDB + * + * @param server Name or IP address of the database server + * @param dbName Name of the database to connect to on the server + * @param port Port number to use for the connection + * @param username Username to use for the database connection + * @param password Password to use for the database connection + */ + @Throws(ClassNotFoundException::class, SQLException::class) + fun setDbInfo(server : String, dbName: String, port: String, username: String, password : String) { + setDbInfo(server, dbName, Integer.parseInt(port), username, password) + } + + /** + * Disconnects from the database + */ + fun disconnect() { + connection.disconnect() + } + + /** + * Queries the database + * + * @param query The MySQL query to run + * + * @return The results of the query + */ + fun runQuery(query: String) : ResultSet? { + return try { + connection.runQuery(query) + } + catch (e: SQLException) { + e.printStackTrace() + null + } + } + + /** + * Run a command on the database which changes existing data (like UPDATE or INSERT) + * + * @param sql The MySQL command to run + * + * @return Number of rows affected + */ + fun runUpdate(sql: String) : Int { + return try { + return connection.runUpdate(sql) + } + catch (e: SQLException) { + e.printStackTrace() + -1 + } + } + + /** + * Gets the current list of coins which are not in a set or book + * + * @return A list of coins in the collection which are not in a set or book + */ + fun getCoins() : ArrayList { + return coins + } + + /** + * Finds a coin with a specific ID in the list and sets it equal to a different coin. + * + * @param id The ID of the coin to find + * @param coin The coin to replace it with. If null, the original coin is removed from the list. + */ + fun setCoin(id: Int, coin: Coin?) { + for(i in 0 until coins.size) { + if(coins[i].id == id) { + if (coin != null) + coins[i] = coin + else + coins.remove(coins[i]) + + return + } + } + } + + /** + * Gets all coins from the database + * + * @return List of coins that fit your query + */ + @Throws(SQLException::class, ClassNotFoundException::class) + private fun getCoinsFromDb(){ + + val query = + """ + SELECT * + FROM Coins + ORDER BY ID; + """.trimIndent() + + + val results: ResultSet? = try { + connection.runQuery(query) + } + catch (sqlE : SQLException) { + sqlE.printStackTrace() + throw sqlE + } + catch (cnf : ClassNotFoundException) { + throw cnf + } + + // Show coins + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + coins = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each coin found + for (i in 0 until size) { + results.next() + + + + val newCoin = Coin() + newCoin.id = results.getInt("ID") + newCoin.countryName = results.getString("CountryName") + // Can't get full currency here because it will start a new query + newCoin.currency.nameAbbr = results.getString("CurrencyAbbr") + newCoin.name = Objects.requireNonNullElse(results.getString("Type"), "") + newCoin.year = results.getInt("Yr") + newCoin.denomination = results.getDouble("Denomination") + newCoin.value = results.getDouble("CurValue") + newCoin.mintMark = Objects.requireNonNullElse(results.getString("MintMark"), "") + newCoin.graded = results.getBoolean("Graded") + newCoin.condition = Objects.requireNonNullElse(results.getString("Grade"), "") + newCoin.error = results.getBoolean("Error") + newCoin.errorType = Objects.requireNonNullElse(results.getString("ErrorType"), "") + newCoin.note = Objects.requireNonNullElse(results.getString("Note"), "") + val setId = Objects.requireNonNullElse(results.getInt("SetID"), DatabaseItem.ID_INVALID) + newCoin.addSetId(setId) + newCoin.slotId = Objects.requireNonNullElse(results.getInt("SlotID"), DatabaseItem.ID_INVALID) + newCoin.containerId = Objects.requireNonNullElse(results.getInt("ContainerID"), DatabaseItem.ID_INVALID) + + if(results.getString("ObvImgExt") != null && results.getString("ObvImgExt") !="") + newCoin.obvImgPath = newCoin.generateImagePath(true, results.getString("ObvImgExt")) + if(results.getString("RevImgExt") != null && results.getString("RevImgExt") !="") + newCoin.revImgPath = newCoin.generateImagePath(false, results.getString("RevImgExt")) + + newCoin.currency = findCurrency(newCoin.currency.nameAbbr) + + if(setId != DatabaseItem.ID_INVALID) + coinsInSets.add(newCoin) + else if(newCoin.slotId != DatabaseItem.ID_INVALID) + coinsInBooks.add(newCoin) + else + coins.add(newCoin) + + } + } + } catch (e: SQLException) { + e.printStackTrace() + throw e + } + + disconnect() + + coinListener?.coinListRetrievedFromFb(coins) + } + + /** + * Gets the current list of bills + * + * @return A list of bills in the collection + */ + fun getBills() : ArrayList { + return bills + } + + /** + * Finds a bill with a specific ID in the list and sets it equal to a different bill. + * + * @param id The ID of the bill to find + * @param bill The bill to replace it with. If null, the original bill is removed from the list. + */ + fun setBill(id: Int, bill: Bill?) { + for(i in 0 until bills.size) { + if(bills[i].id == id) { + if (bill != null) + bills[i] = bill + else + bills.remove(bills[i]) + + return + } + } + } + + /** + * Get all bills from database + * + * @return A list of bills that fit your query + */ + private fun getBillsFromDb(){ + + val query = + """ + SELECT * + FROM Bills + ORDER BY ID; + """.trimIndent() + + val results: ResultSet? = connection.runQuery(query) + + // Show bills + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + bills = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each bill found + for (i in 0 until size) { + results.next() + + val newBill = Bill() + newBill.id = results.getInt("ID") + newBill.countryName = results.getString("CountryName") + newBill.currency.nameAbbr = results.getString("CurrencyAbbr") + newBill.name = Objects.requireNonNullElse(results.getString("Type"), "") + newBill.year = results.getInt("Yr") + newBill.seriesLetter = results.getString("SeriesLetter") + newBill.denomination = results.getDouble("Denomination") + newBill.value = results.getDouble("CurValue") + newBill.graded = results.getBoolean("Graded") + newBill.serial = results.getString("Serial") + newBill.signatures = results.getString("Signatures") + newBill.condition = Objects.requireNonNullElse(results.getString("Grade"), "") + newBill.error = results.getBoolean("Error") + newBill.errorType = Objects.requireNonNullElse(results.getString("ErrorType"), "") + newBill.replacement = results.getBoolean("Replacement") + val setId = Objects.requireNonNullElse(results.getInt("SetID"), DatabaseItem.ID_INVALID) + newBill.addSetId(setId) + newBill.note = Objects.requireNonNullElse(results.getString("Note"), "") + newBill.containerId = Objects.requireNonNullElse(results.getInt("ContainerID"), DatabaseItem.ID_INVALID) + + if(results.getString("ObvImgExt") != null) + newBill.obvImgPath = newBill.generateImagePath(true, results.getString("ObvImgExt")) + if(results.getString("RevImgExt") != null) + newBill.revImgPath = newBill.generateImagePath(false, results.getString("RevImgExt")) + + newBill.currency = findCurrency(newBill.currency.nameAbbr) + + if(setId != DatabaseItem.ID_INVALID) + billsInSets.add(newBill) + else + bills.add(newBill) + } + } + } catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + + billListener?.billListRetrievedFromFb(bills) + } + + /** + * Gets the current list of coin sets + * + * @return A list of coin sets in the collection + */ + fun getSets() : ArrayList { + return sets + } + + /** + * Find all sets + * + * @return List of coin sets that fit your query + */ + private fun getSetsFromDb() { + + val query = + """ + SELECT * + FROM Sets + ORDER BY ID; + """.trimIndent() + + val results: ResultSet? = connection.runQuery(query) + + // Show sets + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + sets = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each set found + for (i in 0 until size) { + results.next() + + val newSet = Set() + newSet.id = results.getInt("ID") + newSet.name = Objects.requireNonNullElse(results.getString("Name"), "") + newSet.year = results.getInt("Yr") + newSet.value = results.getDouble("CurValue") + newSet.note = Objects.requireNonNullElse(results.getString("Note"), "") + val parentId = Objects.requireNonNullElse(results.getInt("ParentID"), DatabaseItem.ID_INVALID) + newSet.addSetId(parentId) + newSet.containerId = Objects.requireNonNullElse(results.getInt("ContainerID"), DatabaseItem.ID_INVALID) + + if(parentId != DatabaseItem.ID_INVALID) + setsInSets.add(newSet) + else + sets.add(newSet) + } + + // Remove coins that are in sets from the coins list + val removedCoins = ArrayList() + + for(coin in coinsInSets) { + if(coin.set != null) { + findSet(coin.set!!.id)?.addItem(coin) + removedCoins.add(coin) + } + } + + coinsInSets.removeAll(removedCoins) + + // Remove bills that are in sets from the bills list + val removedBills = ArrayList() + + for(bill in billsInSets) { + if(bill.set != null) { + findSet(bill.set!!.id)?.addItem(bill) + removedBills.add(bill) + } + } + + bills.removeAll(removedBills) + + // Remove sets that are in other sets from the sets list + val removedSets = ArrayList() + + for(set in setsInSets) { + if(set.set != null) { + findSet(set.set!!.id)?.addItem(set) + removedSets.add(set) + } + } + + setsInSets.removeAll(removedSets) + } + } + catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + + setListener?.setListRetrievedFromFb(sets) + } + + /** + * Gets the current list of countries + * + * @return An ArrayList of countries + */ + fun getCountries() : ArrayList { + return countries + } + + fun findSet(id: Int) : Set? { + for (set in sets) + if (set.id == id) + return set + for (set in setsInSets) + if (set.id == id) + return set + + return null + } + + /** + * Finds a set with a specific ID in the list and sets it equal to a different set. + * + * @param id The ID of the coin set to find + * @param set The set to replace it with. If null, the original set is removed from the list. + */ + fun setSet(id: Int, set: Set?) { + for(i in 0 until sets.size) { + if(sets[i].id == id) { + if (set != null) + sets[i] = set + else + sets.remove(sets[i]) + + return + } + } + } + + /** + * Gets a list of all countries from the database + * + * @return An ArrayList of all countries in the database. The list is sorted alphabetically by name. + */ + private fun getCountriesFromDb() { + val results = connection.runQuery("SELECT * FROM Countries ORDER BY Name ASC") + + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + countries = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each country found, get info + for (i in 0 until size) { + results.next() + + val name = Objects.requireNonNullElse(results.getString("Name"), "") + val id = results.getInt("ID") + val newCountry = Country(name, id, ArrayList()) + + countries.add(newCountry) + } + + Country.sort(countries, topOfCountriesList) + + // Add currencies to countries + // Can't do this above because it ruins the connection / results + for (country in countries) { + country.currencies = getCountryCurrencies(country.name) + } + } + } catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + + countryListener?.countryListChanged(countries) + } + + /** + * Gets the current list of containers + * + * @return An ArrayList of containers + */ + fun getContainers() : ArrayList { + return containers + } + + /** + * Gets all containers from the database + * + * @return A list of all containers. The list is sorted alphabetically by name + */ + private fun getContainersFromDb() { + val results = connection.runQuery( "SELECT * From Containers ORDER BY Name") + + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + containers = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each set found + for (i in 0 until size) { + results.next() + + val container = Container() + + container.id = results.getInt("ID") + container.name = Objects.requireNonNullElse(results.getString("Name"), "") + container.parentID = + Objects.requireNonNullElse(results.getInt("ParentID"), DatabaseItem.ID_INVALID) + + containers.add(container) + } + } + } + catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + } + + /** + * Finds a container with a specific ID in the list and sets it equal to a different container. + * + * @param id The ID of the coin to find + * @param container The container to replace it with. If null, the original container is removed from the list. + */ + fun setContainer(id: Int, container: Container?) { + for(i in 0 until containers.size) { + if(containers[i].id == id) { + if (container != null) + containers[i] = container + else + containers.remove(containers[i]) + + return + } + } + } + + /** + * Finds a container with a specific name + * + * @param name The name of the container to look for + * @return The container with the provided name + */ + fun findContainer(name: String) : Container { + containers = getContainers() + + for (container in containers) { + if(container.name == name) + return container + } + + return Container() + } + + /** + * Finds a container with a specific ID + * + * @param id The ID of the container to look for + * @return The container with the provided ID + */ + fun findContainer(id: Int) : Container { + containers = getContainers() + + for (container in containers) { + if(container.id == id) + return container + } + + return Container() + } + + /** + * Gets the current list of currencies + * + * @return An ArrayList containing all possible currencies + */ + private fun getCurrencies() : ArrayList { + return currencies + } + + /** + * Retrieves all currencies from the Database + */ + private fun getCurrenciesFromDb() { + val results = connection.runQuery("SELECT * From Currencies " + + "ORDER BY Name") + + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + + currencies = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each set found + for (i in 0 until size) { + results.next() + + val id = results.getInt("ID") + val name = Objects.requireNonNullElse(results.getString("Name"), "") + val abbreviation = Objects.requireNonNullElse(results.getString("Abbreviation"), "") + val symbol = Objects.requireNonNullElse(results.getString("Symbol"), "") + val symbolBefore = results.getBoolean("SymbolBefore") + val start = results.getInt("YrStart") + val end : Int = results.getInt("YrEnd") // Will be 0 if null + + currencies.add(Currency(name, id, abbreviation, symbol, symbolBefore, start, end)) + } + } + } + catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + } + + /** + * Gets the currencies used in a specific country + * + * @param countryName The name of the country whose currencies you are looking for + * @return A list of currencies used by the provided country + */ + private fun getCountryCurrencies(countryName: String): ArrayList { + + val results = connection.runQuery("SELECT C.* From Currencies As C\n" + + "JOIN CountryCurrencies As CC\n" + + "ON CC.CurrencyAbbr = C.Abbreviation\n" + + "WHERE CC.CountryName = \"$countryName\"\n" + + "ORDER BY COALESCE(C.YrEnd, 'zz') DESC;") + + val currencies = ArrayList() + + try { + if (results != null) { + // Find out how many rows are in the result + var size = 0 + try { + results.last() + size = results.row + results.beforeFirst() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each set found + for (i in 0 until size) { + results.next() + + val id = results.getInt("ID") + val name = Objects.requireNonNullElse(results.getString("Name"), "") + val abbreviation = Objects.requireNonNullElse(results.getString("Abbreviation"), "") + val symbol = Objects.requireNonNullElse(results.getString("Symbol"), "") + val symbolBefore = results.getBoolean("SymbolBefore") + val start = results.getInt("YrStart") + val end : Int = results.getInt("YrEnd") // Will be 0 if null + + currencies.add(Currency(name, id, abbreviation, symbol, symbolBefore, start, end)) + } + } + } + catch (e: SQLException) { + e.printStackTrace() + } + + disconnect() + + return currencies + } + + /** + * Find a currency based on its unique abbreviation + * + * @param abbr The unique abbreviation of the requested currency + * @return The currency which uses the provided abbreviation + */ + fun findCurrency(abbr: String) : Currency { + currencies = getCurrencies() + + for(currency in currencies) { + if(currency.nameAbbr == abbr) + return currency + } + + return Currency() + } + + /** + * Gets the current list of books + * + * @return A list of books in the collection + */ + fun getBooks() : ArrayList { + return books + } + + /** + * Gets books from the database. Also retrieves the pages, slots, and coins in the books. + * + * @return An ArrayList of books in the database. + */ + private fun getBooksFromDb() { + val results = connection.runQuery("SELECT * From Books") + + if (results != null) { + var size = 0 + + try { + results.last() + size = results.row + results.beforeFirst() + + books = ArrayList() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each book found + for (i in 0 until size) { + results.next() + + val id = results.getInt("ID") + val title = results.getString("Title") + val denomination = Objects.requireNonNullElse(results.getDouble("Denomination"), 0.0) + val startYear = Objects.requireNonNullElse(results.getInt("StartYear"), 0) + val endYear = Objects.requireNonNullElse(results.getInt("EndYear"), 9999) + val containerId = Objects.requireNonNullElse(results.getInt("ContainerID"), DatabaseItem.ID_INVALID) + + val book = Book() + book.id = id + book.title = title + book.denomination = denomination + book.startYear = startYear + book.endYear = endYear + book.containerId = containerId + + books.add(book) + } + } + + disconnect() + + for (book in books) { + val pages = getBookPagesFromDb(book.id) + for(page in pages) { + page.book = book + book.addPage(page) + } + } + + bookListener?.bookListRetrievedFromFb(books) + } + + /** + * Gets a list of pages in a specific book from the database + * + * @param bookId The unique ID of the book + * @return An ArrayList of pages in the provided book + */ + private fun getBookPagesFromDb(bookId: Int) : ArrayList { + val results = connection.runQuery("SELECT R.* From BookPages where BookID = $bookId") + + val pages = ArrayList() + + if (results != null) { + var size = 0 + + try { + results.last() + size = results.row + results.beforeFirst() + } catch (ex: Exception) { + ex.printStackTrace() + } + + // for each set found + for (i in 0 until size) { + results.next() + + val id = results.getInt("ID") + val pageNum = results.getInt("PageNum") + + val page = BookPage() + page.id = id + page.pageNum = pageNum + + pages.add(page) + } + } + + disconnect() + + for(page in pages) { + val rows = getPageSlotsFromDb(page.id) + + // Add rows to page + for(row in rows) { + page.rows.add(row) + // Fill rows with slots + for(slot in row) { + row.add(slot) + val coinsToRemove = ArrayList() + // Fill slots with coins + for (coin in coinsInBooks) { + if (coin.slotId == slot.id) { + slot.coin = coin + coinsToRemove.add(coin) + break + } + } + + for(coin in coinsToRemove){ + coinsInBooks.remove(coin) + } + } + } + } + + return pages + } + + /** + * Gets slots in a specific book page from the database + * + * @param pageId The unique ID of the BookPage + * @return An ArrayList of an ArrayList of Page slots. This list within a list represents rows of slots + */ + private fun getPageSlotsFromDb(pageId: Int) : ArrayList> { + val results = connection.runQuery("SELECT * From PageSlots\n" + + "Where PageID = $pageId\n" + + "ORDER BY RowNum, ColNum") + + val rows = ArrayList>() + + if (results != null) { + var size = 0 + + try { + results.last() + size = results.row + results.beforeFirst() + } catch (ex: Exception) { + ex.printStackTrace() + } + + results.next() + + var currentRowNum = 1 + var currentRow = ArrayList() + + // For each slot found + for (i in 0 until size) { + + val id = results.getInt("ID") + val rowNum = results.getInt("RowNum") + val colNum = results.getInt("ColNum") + val denomination = Objects.requireNonNullElse(results.getDouble("Denomination"), 0.10) + val label = Objects.requireNonNullElse(results.getString("Label"), "") + val label2 = Objects.requireNonNullElse(results.getString("Label2"), "") + val label3 = Objects.requireNonNullElse(results.getString("Label3"), "") + + val slot = PageSlot() + slot.id = id + slot.rowNum = rowNum + slot.colNum = colNum + slot.line1Text = label + slot.line2Text = label2 + slot.line3Text = label3 + + slot.size = when(denomination) { + .01 -> { PageSlot.SIZE_PENNY} + .05 -> { PageSlot.SIZE_NICKEL } + .10 -> { PageSlot.SIZE_DIME } + .25 -> { PageSlot.SIZE_QUARTER } + .5 -> { PageSlot.SIZE_HALF } + else -> { PageSlot.SIZE_DOLLAR } + } + + // If we started a new row + if(rowNum == currentRowNum) { + rows.add(currentRow) + currentRow = ArrayList() + currentRowNum++ + } + + currentRow.add(slot) + } + + rows.add(currentRow) + } + + disconnect() + + return rows + } + + /** + * Finds a book with a specific ID in the list and sets it equal to a different book. + * + * @param id The ID of the coin to find + * @param book The book to replace it with. If null, the original book is removed from the list. + */ + fun setBook(id: Int, book: Book?) { + for(i in 0 until books.size) { + if(books[i].id == id) { + if (book != null) + books[i] = book + else + books.remove(books[i]) + + return + } + } + } + + /** + * Imports a coin folder from an XML file + * + * @param path The location of the XML file to import + * @return The book that was created from the XML input file + */ + fun importBook(path: String): Book { + + fun processNode(node: Node, book: Book): Any { + + when (node.nodeName.toLowerCase()) { + "page" -> { + val page = BookPage() + + for (newChild in 0 until node.childNodes.length) { + val item = processNode(node.childNodes.item(newChild), book) + + // if it's a row + if (item is ArrayList<*> && item[0] is PageSlot) { + page.rows.add(item as ArrayList) + for (slot in item) { + slot.bookPage = page + } + } + } + + return page + } + "row" -> { + val row = ArrayList() + + for (newChild in 0 until node.childNodes.length) { + val item = processNode(node.childNodes.item(newChild), book) + + // if it's a row + if (item is PageSlot) { + row.add(item) + } + } + + return row + } + "slot" -> { + var line1Text = "" + var line2Text = "" + var size = "" + + if (node.nodeType == Node.ELEMENT_NODE) { + val element = node as Element + if (element.hasAttribute("line1")) + line1Text = element.getAttribute("line1") + if (element.hasAttribute("line2")) + line2Text = element.getAttribute("line2") + if (element.hasAttribute("size")) + size = element.getAttribute("size") + } + + val sizeInt = if(size == "") { + when (book.denomination) { + .01 -> {PageSlot.SIZE_PENNY} + .05 -> {PageSlot.SIZE_NICKEL} + .1 -> {PageSlot.SIZE_DIME} + .25 -> {PageSlot.SIZE_QUARTER} + .5 -> {PageSlot.SIZE_HALF} + else -> {PageSlot.SIZE_DOLLAR} + } + } + else + when (size.toLowerCase()) { + "penny" -> {PageSlot.SIZE_PENNY} + "cent" -> {PageSlot.SIZE_PENNY} + "nickel" -> {PageSlot.SIZE_NICKEL} + "dime" -> {PageSlot.SIZE_DIME} + "quarter" -> {PageSlot.SIZE_QUARTER} + "half" -> {PageSlot.SIZE_HALF} + else -> {PageSlot.SIZE_DOLLAR} + } + + return PageSlot(sizeInt, line1Text, line2Text) + } + } + + return "" + } + + val book = Book() + + val file = getFileFromRes(path) + + if(file == null || !file.exists()) + return Book() + + val dbf: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + val db: DocumentBuilder = dbf.newDocumentBuilder() + val document: Document = db.parse(file) + document.documentElement.normalize() + + val bookNode = document.getElementsByTagName("Book") + + if (bookNode.item(0).nodeType == Node.ELEMENT_NODE) { + val element = bookNode.item(0) as Element + if (element.hasAttribute("title")) + book.title = element.getAttribute("title") + if (element.hasAttribute("startYear")) + book.startYear = element.getAttribute("startYear").toInt() + if (element.hasAttribute("endYear")) + book.endYear = element.getAttribute("endYear").toInt() + if (element.hasAttribute("denomination")) + book.denomination = element.getAttribute("denomination").toDouble() + } + + val pageNodes = document.getElementsByTagName("Page") + + for (pageNum in 1 .. pageNodes.length) { + val page = processNode(pageNodes.item(pageNum - 1), book) + + (page as BookPage).pageNum = pageNum + page.book = book + book.pages.add(page) + } + + return book + } + + /** + * Imports a list of countries from an XML file + * + * @param path The location of the XML file to import + * @return An ArrayList containing the countries from the XML input file + */ + fun importCountries(path: String) : ArrayList { + + fun parseXmlNode(node: Node) : Any { + + when (node.nodeName.toLowerCase()) { + "country" -> { + val country = Country() + if (node.nodeType == Node.ELEMENT_NODE) { + val element = node as Element + if (element.hasAttribute("name")) + country.name = element.getAttribute("name") + } + + for(child in 0 until node.childNodes.length) { + try { + country.currencies.add(parseXmlNode(node.childNodes.item(child)) as Currency) + } catch (e: Exception) { + + } + } + + country.currencies = Currency.sort(country.currencies) + + return country + } + "currency" -> { + val currency = Currency() + + if (node.nodeType == Node.ELEMENT_NODE) { + val element = node as Element + if (element.hasAttribute("name")) + currency.name = element.getAttribute("name") + if (element.hasAttribute("abbr")) + currency.nameAbbr = element.getAttribute("abbr") + if (element.hasAttribute("symbol")) + currency.symbol = element.getAttribute("symbol") + if (element.hasAttribute("symbol_before")) + currency.symbolBefore = element.getAttribute("symbol_before").equals("true") + if (element.hasAttribute("start_year")) + currency.yrStart = element.getAttribute("start_year").toInt() + if (element.hasAttribute("end_year")) + currency.yrEnd = element.getAttribute("end_year").toInt() + } + + return currency + } + else -> return "" + } + } + + val countries = ArrayList() + + val file = File(path) + + val dbf: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + val db: DocumentBuilder = dbf.newDocumentBuilder() + val document: Document = db.parse(file) + document.documentElement.normalize() + + val countryNodes = document.getElementsByTagName("Country") + + for(index in 0 until countryNodes.length) { + countries.add(parseXmlNode(countryNodes.item(index)) as Country) + } + + return Country.sort(countries, topOfCountriesList) + } + + /** + * Finds the coin in a specific slot in a book + * + * @param slot The slot to investigate + * @return The coin in the slot. If the coin had an ID of CollectionItem.INVALID_ID then the slot is empty. + */ + fun findCoinInSlot(slot: PageSlot) : Coin { + + for(coin in coins) { + if(coin.slotId == slot.id) + return coin + } + + return Coin() + } + + /** + * Returns status of an update or insert. Implies that only 1 row should be updated at a time. + * + * @param rows How many rows were modified by the insert or update + * @return A message about whether the command was successful + */ + fun getSuccessMessage(rows: Int): String { + return connection.wasSuccessful(rows) + } + + fun getGeneratedKeys() : ResultSet { + return connection.statement!!.generatedKeys + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Bill.kt b/NumismatistAPI/src/items/Bill.kt new file mode 100644 index 0000000..646a5d7 --- /dev/null +++ b/NumismatistAPI/src/items/Bill.kt @@ -0,0 +1,143 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.sql.SQLException + +class Bill : SetItem() { + + var seriesLetter = "" + + var serial = "" + var signatures = "" + var replacement = false + + companion object { + val CONDITIONS = arrayOf("", "G-4", "VG-8", "F-12", "VF-20", "EF-40", "AU-50", "UC-63") + } + + /** + * Copies the bill + * + * @return A copy of this bill + */ + override fun copy() : Bill { + val newBill = Bill() + + newBill.countryName = countryName + newBill.currency = currency + newBill.name = name + newBill.year = year + newBill.seriesLetter = seriesLetter + newBill.denomination = denomination + newBill.value = value + newBill.set = set + newBill.graded = graded + newBill.condition = condition + newBill.note = note + newBill.obvImgPath = obvImgPath + newBill.revImgPath = revImgPath + newBill.containerId = containerId + + return newBill + } + + override fun toString(): String { + var out = "$year" + + if(seriesLetter != "") + out += "-$seriesLetter" + + out += " $name" + + return out + } + + /** + * Saves this bill to the database. If the bill is not already in the database (id = 0) the bill is added. + * If the bill is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + override fun saveToDb(api: NumismatistAPI): Int { + + // set SetID to null if necessary + val newSetID = if(set == null) + "null" + else + "" + set!!.id + + val containerID = if(containerId == ID_INVALID) + "null" + else + "" + containerId + + val sql : String + val rows : Int + if(id != 0) { + + sql = "UPDATE Bills SET CountryName=\"${countryName}\", CurrencyAbbr=\"${currency.nameAbbr}\", Type=\"${name}\", Yr=${year}, " + + "Denomination=${denomination}, CurValue=${value}, Graded=${graded}, Grade=\"${condition}\", " + + "SeriesLetter=\"${seriesLetter}\", Serial=\"${serial}\", Signatures=\"${signatures}\", ContainerID=$containerID, " + + "Error=${error}, ErrorType=\"${errorType}\", Note=\"${note}\", SetID=$newSetID, Replacement=${replacement}\n" + + "WHERE ID=${id};" + + rows = api.runUpdate(sql) + + if(rows == 1) + api.setBill(id, this) + } + else { + + sql = "INSERT INTO Bills(CountryName, CurrencyAbbr, Type, Yr, SeriesLetter, Denomination, CurValue, Graded, Grade, " + + "Error, ErrorType, Serial, Signatures, ContainerID, Note, SetID, Replacement)\n" + + "VALUES(\"${countryName}\", \"${currency.nameAbbr}\", \"${name}\", ${year}, \"${seriesLetter}\", ${denomination}, ${value}, " + + "${graded}, \"${condition}\", ${error}, \"${errorType}\", \"${serial}\", \"${signatures}\", " + + "$containerID, \"${note}\", $newSetID, ${replacement});" + + rows = api.runUpdate(sql) + + // If successful, set the new ID to this object + if(rows == 1) { + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + api.getBills().add(this) + } + } + + api.disconnect() + + return rows + } + + /** + * Removes this bill from the database + * + * @param api an API object to use to connect to the database + * + * @return True if bill was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM Bills WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + api.disconnect() + + if(rows==1) { + api.getBills().remove(this) + + // Delete pictures + deleteObvImage() + deleteRevImage() + } + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Book.kt b/NumismatistAPI/src/items/Book.kt new file mode 100644 index 0000000..378b0c8 --- /dev/null +++ b/NumismatistAPI/src/items/Book.kt @@ -0,0 +1,141 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.sql.SQLException + +/** + * A book (or folder) that holds loose coins. These books often have multiple pages, and those pages have multiple + * slots that hold Coins. Each slot can only hold a single coin. + * + * @see BookPage + * @see PageSlot + * @see Coin + */ +class Book : CollectionItem() { + + companion object { + /** + * Use this as startYear if the book has no starting year + */ + const val DEFAULT_START_YEAR = 0 + /** + * Use this as endYear if the book has no ending year + */ + const val DEFAULT_END_YEAR = 9999 + } + + var title = "" + /** + * An optional parameter, used if every coin is the same denomination. Otherwise, use SetItem.NO_DENOMINATION + */ + var denomination = SetItem.NO_DENOMINATION + var startYear = DEFAULT_START_YEAR + var endYear = DEFAULT_END_YEAR + + val pages = ArrayList() + + /** + * Adds a page to this book + * + * @param page The page you want to add + */ + fun addPage(page: BookPage) { + pages.add(page) + } + + /** + * Saves a book to the database. If the book is not already in the database (id = 0) the book is added. + * If the book is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + override fun saveToDb(api: NumismatistAPI) : Int { + + val sql: String + + val rows: Int + + // set ContainerID to null if necessary + val containerID = if(containerId == ID_INVALID) + "null" + else + "" + containerId + + if(id != 0) { + + sql = "UPDATE Books SET Title=\"${title}\", Denomination=${denomination}, " + + "StartYear=${startYear}, EndYear=${endYear}, ContainerID=$containerID\n" + + "WHERE ID=${id};" + + rows = api.runUpdate(sql) + + if(rows == 1) { + for(page in pages) { + page.saveToDb(api) + } + + api.setBook(id, this) + } + } + else { + + sql = "INSERT INTO Books(Title, Denomination, StartYear, EndYear, ContainerID)\n" + + "VALUES(\"${title}\", ${denomination}, ${startYear}, ${endYear}, " + + "$containerID);" + + rows = api.runUpdate(sql) + + // If successful, set the new ID to this object + if(rows == 1) { + val newID = api.getGeneratedKeys() + + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + + for(page in pages) { + // TODO: Handle Errors + page.saveToDb(api) + } + + // TODO: if no errors + api.getBooks().add(this) + } + } + + api.disconnect() + + return rows + } + + /** + * Removes a book from the database + * + * @param api an API object to use to connect to the database + * + * @return True if book was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM Books WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + api.disconnect() + + if(rows==1) { + // Remove the pages in the book + // TODO: Handle Error + for(page in pages) + page.removeFromDb(api) + + api.getBooks().remove(this) + } + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/BookPage.kt b/NumismatistAPI/src/items/BookPage.kt new file mode 100644 index 0000000..5ef34eb --- /dev/null +++ b/NumismatistAPI/src/items/BookPage.kt @@ -0,0 +1,113 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.sql.SQLException + +/** + * A page of a Book (or folder) that holds Coins. Each page can have multiple slots. Each slot holds a single coin. + * + * @see Book + * @see PageSlot + * @see Coin + */ +class BookPage : DatabaseItem() { + + /** + * The book that this page is in + */ + var book = Book() + /** + * Page number of this page inside the book + */ + var pageNum = 0 + + /** + * Contains a list of coins which the user has selected to remove from the database + */ + val coinsToDelete = ArrayList() + + val rows = ArrayList>() + + /** + * Saves a book page to the database. If the page is not already in the database (id = 0) the page is added. + * If the page is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + override fun saveToDb(api: NumismatistAPI) : Int { + + val sql : String + + val rows : Int + + if(id != ID_INVALID) { + sql = "UPDATE BookPages SET BookID=${book.id}, PageNum=${pageNum}" + + "WHERE ID=${id};" + + rows = api.runUpdate(sql) + + if(rows ==1) { + for (row in this.rows) { + for (slot in row) + // TODO: Handle Errors + slot.saveToDb(api) + } + } + } + else { + + sql = "INSERT INTO BookPages(BookID, PageNum)\n" + + "VALUES(${book.id}, ${pageNum});" + + rows = api.runUpdate(sql) + + // If successful, set the new ID to this object + if(rows == 1) { + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + + for (row in this.rows) { + for (slot in row) { + // TODO: Handle Errors + slot.saveToDb(api) + } + } + } + } + + return rows + } + + /** + * Removes a book from the database + * + * @param api an API object to use to connect to the database + * + * @return True if book was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM BookPages WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + api.disconnect() + + if(rows==1) { + // Remove Page Slots + // TODO: Handle Error + for(row in this.rows) { + for(slot in row) + slot.removeFromDb(api) + } + } + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Coin.kt b/NumismatistAPI/src/items/Coin.kt new file mode 100644 index 0000000..7cd1484 --- /dev/null +++ b/NumismatistAPI/src/items/Coin.kt @@ -0,0 +1,185 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.sql.SQLException + +class Coin : SetItem() +{ + companion object { + const val HALF_PENNY = 0.005 + const val PENNY = 0.01 + const val NICKEL = 0.05 + const val DIME = 0.10 + const val QUARTER = 0.25 + const val HALF_DOLLAR = 0.50 + const val DOLLAR = 1.0 + + val MINT_MARKS = arrayOf("", "C", "CC", "D", "O", "P", "S", "W") + val CONDITIONS = arrayOf("", "AG-3", "G-4", "VG-8", "F-12", "VF-20", "EF-40", "AU-50", "MS-60", "MS-63", "MS-65", "MS-66", "MS-67", "PF-67") + } + + var mintMark = "" + set(value) { + field = if(value.length <= 3) + value + else + value.substring(0 .. 2) + } + + /** + * ID of the book slot this coin is in. If set to ID_INVALID then it's not in a book + */ + var slotId : Int = ID_INVALID + + /** + * Creates a copy of this coin. + * + * @return A copy of this coin + */ + override fun copy() : Coin { + val newCoin = Coin() + + newCoin.id = id + newCoin.countryName = countryName + newCoin.currency = currency + newCoin.year = year + newCoin.denomination = denomination + newCoin.mintMark = mintMark + newCoin.graded = graded + newCoin.condition = condition + newCoin.value = value + newCoin.note = note + newCoin.name = name + newCoin.set = set + + newCoin.obvImgPath = obvImgPath + newCoin.revImgPath = revImgPath + newCoin.slotId = slotId + newCoin.containerId = containerId + + return newCoin + } + + /** + * @return A string in the form of "[-] " + */ + override fun toString() : String { + + var string = "$year" + if(mintMark != "") + string += "-$mintMark" + + string += " $name" + + return string + } + + /** + * Saves this coin to the database. If the coin is not already in the database (id = 0) the coin is added. + * If the coin is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + override fun saveToDb(api: NumismatistAPI) : Int { + + val sql: String + + val rows: Int + + // set SetID to null if necessary + val newSetID = if(set == null) + "null" + else + "" + set!!.id + + // set SlotID to null if necessary + val slotID = if(slotId == ID_INVALID) + "null" + else + "" + slotId + + // set ContainerID to null if necessary + val containerID = if(containerId == ID_INVALID) + "null" + else + "" + containerId + + val obvImgExt = if(obvImgPath != "") + getObvImageExtFromPath() + else + "" + + val revImgExt = if(revImgPath != "") + getRevImageExtFromPath() + else + "" + + if(id != 0) { + + sql = "UPDATE Coins SET CountryName=\"${countryName}\", CurrencyAbbr=\"${currency.nameAbbr}\", Type=\"${name}\", Yr=${year}, " + + "Denomination=${denomination}, CurValue=${value}, MintMark=\"${mintMark}\", Graded=${graded}, " + + "Grade=\"${condition}\", Error=${error}, ErrorType=\"${errorType}\", SetID=$newSetID, " + + "ObvImgExt=\"$obvImgExt\", RevImgExt=\"$revImgExt\", Note=\"${note}\", SlotID=$slotID, " + + "ContainerID=$containerID " + + "WHERE ID=${id};" + + rows = api.runUpdate(sql) + + if(rows == 1) { + api.setCoin(id, this) + } + } + else { + + sql = "INSERT INTO Coins(CountryName, CurrencyAbbr, Type, Yr, Denomination, CurValue, MintMark, Graded," + + " Grade, Error, ErrorType, SetID, SlotID, ContainerID, ObvImgExt, RevImgExt, Note)\n" + + "VALUES(\"${countryName}\", \"${currency.nameAbbr}\", \"${name}\", ${year}, ${denomination}, ${value}, \"${mintMark.toUpperCase()}\", ${graded}," + + " \"${condition}\", ${error}, \"${errorType}\", $newSetID, $slotID, $containerID, \"$obvImgExt\", \"$revImgExt\", \"${note}\");" + + rows = api.runUpdate(sql) + + // If successful, set the new ID to this object + if(rows == 1) { + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + api.getCoins().add(this) + } + } + + api.disconnect() + + return rows + } + + /** + * Removes this coin from the database + * + * @param api an API object to use to connect to the database + * + * @return True if coin was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM Coins WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + api.disconnect() + + if(rows==1) { + api.getCoins().remove(this) + + // Delete pictures + deleteObvImage() + deleteRevImage() + } + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/CollectionItem.kt b/NumismatistAPI/src/items/CollectionItem.kt new file mode 100644 index 0000000..29f4cf9 --- /dev/null +++ b/NumismatistAPI/src/items/CollectionItem.kt @@ -0,0 +1,266 @@ +package items + +import NumismatistAPI +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException + +/** + * CollectionItems are DatabaseItems which can have images attached to them, and can be put in a Container, such as Books. + * Images can be of the obverse (front) or reverse (back) of the item. + * + * Subclasses include SetItems + * + * @see DatabaseItem + * @see SetItem + * @see Container + * @see Book + */ +@Suppress("unused") +abstract class CollectionItem : DatabaseItem() { + + /** + * Location of the image of the front of the item, including file extension + */ + var obvImgPath = "" + set(value) { + if(oldObvPath=="") + oldObvPath = field + field = value + } + private var oldObvPath = "" + + /** + * Location of the image of the back of the item, including file extension + */ + var revImgPath = "" + set(value) { + if(oldRevPath=="") + oldRevPath = field + field = value + } + private var oldRevPath = "" + var note = "" + + /** + * ID of a container this item is in. If not in a container, should be ID_INVALID + */ + var containerId = ID_INVALID + + /** + * Gets the file extension of the image file for the front of the item + * + * @return File extension + */ + fun getObvImageExtFromPath() : String { + if(obvImgPath.contains(".")) + return obvImgPath.substring(obvImgPath.lastIndexOf('.')) + + return "" + } + + /** + * Gets the file extension of the image file for the back of the item + * + * @return File extension + */ + fun getRevImageExtFromPath() : String { + if(revImgPath.contains(".")) + return revImgPath.substring(revImgPath.lastIndexOf('.')) + + return "" + } + + /** + * Gets the default image path for an item + * + * @param obverse True if looking for path of image of front of the item, otherwise false + * @param ext The file extension of the image file + */ + fun generateImagePath(obverse: Boolean, ext: String) : String { + if(this is Coin) { + return if (obverse) + "coin-$id-obv.$ext" + else + "coin-$id-rev.$ext" + } + if(this is Set) { + return if (obverse) + "set-$id-obv.$ext" + else + "set-$id-rev.$ext" + } + if(this is Bill) { + return if (obverse) + "bill-$id-obv.$ext" + else + "bill-$id-rev.$ext" + } + + return if(obverse) + "item-$id-obv.$ext" + else + "item-$id-rev.$ext" + } + + /** + * Saves both images to the file system + * + * @return True if save successful, otherwise false + */ + @Throws(SecurityException::class, IOException::class, FileNotFoundException::class) + fun saveImages() : Boolean { + return saveObvImage() && saveRevImage() + } + + /** + * Saves image of the front of the item to the file system + * + * @return True if save successful, otherwise false + */ + @Throws(SecurityException::class, IOException::class, FileNotFoundException::class) + fun saveObvImage() : Boolean { + + // Delete old file + if(File(oldObvPath).exists()) { + try { + File(oldObvPath).delete() + } + catch (se : SecurityException) { + throw se + } + + oldObvPath = "" + } + + // Copy the image to a new location, then use that location + if (obvImgPath != "" ) { + val success = if (File(obvImgPath).exists()) { + try { + !NumismatistAPI.copyFile( + obvImgPath, + generateImagePath(true, getObvImageExtFromPath())) + } + catch (ioe: IOException) { + throw ioe + } + } + else { + val fnf = FileNotFoundException(obvImgPath) + throw fnf + } + + return success + } + + return true + } + + /** + * Saves image of the back of the item to the file system + * + * @return True if save successful, otherwise false + */ + @Throws(SecurityException::class, IOException::class, FileNotFoundException::class) + fun saveRevImage() : Boolean { + // Delete old file + if(File(oldRevPath).exists()) { + try { + File(oldRevPath).delete() + } + catch (se : SecurityException) { + throw se + } + + oldRevPath = "" + } + + // Copy the image to a new location, then use that location + if (revImgPath != "" ) { + val success = if (File(revImgPath).exists()) { + try { + !NumismatistAPI.copyFile( + revImgPath, + generateImagePath(false, getRevImageExtFromPath())) + } + catch (ioe: IOException) { + throw ioe + } + } + else { + val fnf = FileNotFoundException(revImgPath) + throw fnf + } + + return success + } + + return true + } + + /** + * Deletes both images from the file system + * + * @return True if delete is successful, or if no file path is set. Otherwise false + */ + @Throws(FileNotFoundException::class) + fun deleteImages() : Boolean { + return deleteObvImage() && deleteRevImage() + } + + /** + * Deletes image of the front of the item from the file system + * + * @return True if delete is successful, or if no file path is set. Otherwise false + */ + @Throws(FileNotFoundException::class) + fun deleteObvImage() : Boolean { + + if(obvImgPath != "" && obvImgPath == generateImagePath(true, getObvImageExtFromPath())) { + val obvFile = File(obvImgPath) + + // Delete file + if(obvFile.exists()) { + val success = obvFile.delete() + if(success) + obvImgPath = "" + + return success + } + else { + val fnf = FileNotFoundException(obvImgPath) + throw fnf + } + } + + return true + } + + /** + * Deletes image of the front of the item from the file system + * + * @return True if delete is successful, or if no file path is set. Otherwise false + */ + @Throws(FileNotFoundException::class) + fun deleteRevImage() : Boolean { + if(revImgPath != "" && revImgPath == generateImagePath(false, getRevImageExtFromPath())) { + val obvFile = File(revImgPath) + + // Delete file + if(obvFile.exists()) { + val success = obvFile.delete() + if(success) + revImgPath = "" + + return success + } + else { + val fnf = FileNotFoundException(revImgPath) + throw fnf + } + } + + return true + } + +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Container.kt b/NumismatistAPI/src/items/Container.kt new file mode 100644 index 0000000..64f224a --- /dev/null +++ b/NumismatistAPI/src/items/Container.kt @@ -0,0 +1,137 @@ +package items + +import NumismatistAPI + +/** + * Containers are boxes or other containers which contain CollectionItems + * + * @see CollectionItem + */ +class Container : DatabaseItem() { + + var name = "" + + /** + * Unique ID of the container that holds this container. This container is not inside another container if this + * parentID is DatabaseItem.ID_INVALID + */ + var parentID = ID_INVALID + + /** + * Given a list of containers, find containers which are inside (or children of) this one + * + * @param containers A list of possible children + * + * @return Containers which are inside (children of) this one + */ + fun getChildContainers(containers : ArrayList) : ArrayList { + val children = ArrayList() + + for (cont in containers) { + if(cont.parentID == id) + children.add(cont) + } + + return children + } + + /** + * Find coins that are inside a specific container and its children + * + * @param api NumismatistAPI that holds list of containers + * @param includeChildren True if you want to get items inside children containers. Otherwise false + * + * @return A list of items inside the container and optionally inside its children + */ + fun getCoinsInContainer(api: NumismatistAPI, includeChildren: Boolean = true) : ArrayList { + + val childrenIDs = ArrayList() + val children = getChildContainers(api.getContainers()) + // Find the IDs of all child containers + if (includeChildren) + for (child in children) + childrenIDs.add(child.id) + + // A list of coins in this container + val containerItems = ArrayList() + + val allItems = ArrayList() + + allItems.addAll(api.getCoins()) + allItems.addAll(api.getBills()) + allItems.addAll(api.getSets()) + allItems.addAll(api.getBooks()) + + for(item in allItems) { + // If the coin is in this container or its children + if(item.containerId == id || + (includeChildren && childrenIDs.contains(item.containerId))) { + containerItems.add(item) + } + } + + return containerItems + } + + /** + * Save a container to the database + * + * @param api an API object to use to connect to the database + * + * @return Number of rows affected in the database. This should always be 1, otherwise a problem occurred + */ + override fun saveToDb(api: NumismatistAPI): Int { + + val parentID = if(parentID == ID_INVALID) + "null" + else + "" + parentID + + val rows = if(id != 0) { + api.setContainer(id, this) + + api.runUpdate("UPDATE Containers SET Name=${name}, ParentID=$parentID" + + "WHERE ID=${id};") + } + else { + + val newRows = api.runUpdate("INSERT INTO Containers(Name, ParentID)\n" + + "VALUES(\"${name}\", $parentID);") + + if(newRows == 1) { + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + api.getContainers().add(this) + } + + newRows + } + + api.disconnect() + + return rows + } + + /** + * Removes a container from the database + * + * @param api an API object to use to connect to the database + * + * @return True if container was successfully removed, otherwise false + */ + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM Containers WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + if(rows==1) + api.getContainers().remove(this) + + api.disconnect() + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Country.kt b/NumismatistAPI/src/items/Country.kt new file mode 100644 index 0000000..73fb202 --- /dev/null +++ b/NumismatistAPI/src/items/Country.kt @@ -0,0 +1,293 @@ +package items + +import kotlin.collections.ArrayList + +class Country() { + + var name = "" + /** + * Unique ID for this item. Provided by the SQL database + */ + var id = 0 + /** + * A list of currencies that this country has had + */ + var currencies = ArrayList() + + constructor(name: String, id: Int, currencies: ArrayList) : this() { + this.name = name + this.id = id + this.currencies = currencies + } + + companion object { + + /** + * Sorts a list of countries by name + * + * @param countries A list of countries to sort + * @param customOrder A list of countries to leave at the top of the list, disregarding alphabetical order + * + * @return A sorted list of countries + */ + fun sort(countries: ArrayList, customOrder : ArrayList) : ArrayList { + + // Put customs at the beginning + // Add custom list as the first list item, in reverse order. Which results in them + // being in the order we want them to be. + for(i in customOrder.size - 1 downTo 0 ) { + for (j in 0 until countries.size) { + if(countries[j].name == customOrder[i]) { + val country = countries[j] + countries.remove(country) + countries.add(0, country) + // Break out of inner for loop + break + } + } + } + + // Alphabetize the rest + do { + var moves = 0 + // - 1 is necessary because of the + 1 in the next line + for (i in customOrder.size until countries.size - 1) { + if (countries[i].name > countries[i + 1].name) { + val country = countries[i] + countries[i] = countries[i+1] + countries[i+1] = country + moves++ + } + } + }while (moves > 0) + + return countries + } + + /** + * Finds a specific country object + * + * @param countries A list of countries to search through + * @param name The name of the country to find + * + * @return A country with the name provided. If not found, returns a new Country object with no name + */ + fun getCountry(countries : ArrayList, name: String) : Country { + for(country in countries) { + if(country.name == name) + return country + } + + return Country() + } + + /*fun createList(): ArrayList { + + val euro = Currency("Euro", 0, "EUR", "€") + val usd = Currency("US Dollar", 0, "USD", "$") + + // List of countries + return arrayListOf( + //Country("United States (USA)", arrayListOf(usd)), + //Country("Canada", arrayListOf(Currency("Canadian Dollar", "CAD", "$"))), + //Country("Mexico", arrayListOf(Currency("Peso", "MXN", "$"))), + //Country("Afghanistan", arrayListOf()), + //Country("Albania", arrayListOf()), + //Country("Algeria", arrayListOf()), + Country("Andorra", arrayListOf()), + Country("Angola", arrayListOf()), + Country("Anguilla", arrayListOf()), + Country("Antigua & Barbuda", arrayListOf()), + Country("Argentina", arrayListOf()), + Country("Armenia", arrayListOf()), + Country("Australia", arrayListOf()), + Country("Austria", arrayListOf(euro)), + Country("Azerbaijan", arrayListOf()), + Country("Bahamas", arrayListOf()), + Country("Bahrain", arrayListOf()), + Country("Bangladesh", arrayListOf()), + Country("Barbados", arrayListOf()), + Country("Belarus", arrayListOf()), + Country("Belgium", arrayListOf()), + Country("Belize", arrayListOf()), + Country("Benin", arrayListOf()), + Country("Bermuda", arrayListOf()), + Country("Bhutan", arrayListOf()), + Country("Bolivia", arrayListOf()), + Country("Bosnia & Herzegovina", arrayListOf()), + Country("Botswana", arrayListOf()), + Country("Brazil", arrayListOf()), + Country("Brunei Darussalam", arrayListOf()), + Country("Bulgaria", arrayListOf()), + Country("Burkina Faso", arrayListOf()), + Country("Burundi", arrayListOf()), + Country("Cambodia", arrayListOf()), + Country("Cameroon", arrayListOf()), + Country("Cape Verde", arrayListOf()), + Country("Cayman Islands", arrayListOf()), + Country("Central African Republic", arrayListOf()), + Country("Chad", arrayListOf()), + Country("Chile", arrayListOf()), + Country("China", arrayListOf()), + Country("China - Hong Kong / Macau", arrayListOf()), + Country("Colombia", arrayListOf()), + Country("Comoros", arrayListOf()), + Country("Congo", arrayListOf()), + Country("Congo, Democratic Republic of (DRC)", arrayListOf()), + Country("Costa Rica", arrayListOf()), + Country("Croatia", arrayListOf()), + Country("Cuba", arrayListOf()), + Country("Cyprus", arrayListOf()), + Country("Czech Republic", arrayListOf()), + Country("Denmark", arrayListOf()), + Country("Djibouti", arrayListOf()), + Country("Dominica", arrayListOf()), + Country("Dominican Republic", arrayListOf()), + Country("Ecuador", arrayListOf()), + Country("Egypt", arrayListOf()), + Country("El Salvador", arrayListOf()), + Country("Equatorial Guinea", arrayListOf()), + Country("Eritrea", arrayListOf()), + Country("Estonia", arrayListOf()), + Country("Eswatini", arrayListOf()), + Country("Ethiopia", arrayListOf()), + Country("Fiji", arrayListOf()), + Country("Finland", arrayListOf()), + Country("France", arrayListOf(euro)), + Country("French Guiana", arrayListOf()), + Country("Gabon", arrayListOf()), + Country("Gambia, Republic of The", arrayListOf()), + Country("Georgia", arrayListOf()), + //Country("Germany", arrayListOf(euro, Currency("Deutschmark", "DM", "DM"), Currency("Reichsmark", "RM", "ℛℳ", false))), + Country("Ghana", arrayListOf()), + Country("Great Britain", arrayListOf(euro)), + Country("Greece", arrayListOf(euro)), + Country("Grenada", arrayListOf()), + Country("Guadeloupe", arrayListOf()), + Country("Guatemala", arrayListOf()), + Country("Guinea", arrayListOf()), + Country("Guinea-Bissau", arrayListOf()), + Country("Guyana", arrayListOf()), + Country("Haiti", arrayListOf()), + Country("Honduras", arrayListOf()), + Country("Hungary", arrayListOf(euro)), + Country("Iceland", arrayListOf()), + Country("India", arrayListOf()), + Country("Indonesia", arrayListOf()), + Country("Iran", arrayListOf()), + Country("Iraq", arrayListOf()), + Country("Israel and the Occupied Territories", arrayListOf()), + Country("Italy", arrayListOf(euro, Currency("Lira", "ITL", "₤"))), + Country("Ivory Coast (Cote d'Ivoire)", arrayListOf()), + Country("Jamaica", arrayListOf()), + Country("Japan", arrayListOf()), + Country("Jordan", arrayListOf()), + Country("Kazakhstan", arrayListOf()), + Country("Kenya", arrayListOf()), + Country("Korea, Democratic Republic of (North Korea)", arrayListOf()), + Country("Korea, Republic of (South Korea)", arrayListOf()), + Country("Kosovo", arrayListOf()), + Country("Kuwait", arrayListOf()), + Country("Kyrgyz Republic (Kyrgyzstan)", arrayListOf()), + Country("Laos", arrayListOf()), + Country("Latvia", arrayListOf()), + Country("Lebanon", arrayListOf()), + Country("Lesotho", arrayListOf()), + Country("Liberia", arrayListOf()), + Country("Libya", arrayListOf()), + Country("Liechtenstein", arrayListOf()), + Country("Lithuania", arrayListOf()), + Country("Luxembourg", arrayListOf()), + Country("Madagascar", arrayListOf()), + Country("Malawi", arrayListOf()), + Country("Malaysia", arrayListOf()), + Country("Maldives", arrayListOf()), + Country("Mali", arrayListOf()), + Country("Malta", arrayListOf()), + Country("Martinique", arrayListOf()), + Country("Mauritania", arrayListOf()), + Country("Mauritius", arrayListOf()), + Country("Mayotte", arrayListOf()), + Country("Moldova, Republic of", arrayListOf()), + Country("Monaco", arrayListOf()), + Country("Mongolia", arrayListOf()), + Country("Montenegro", arrayListOf()), + Country("Montserrat", arrayListOf()), + Country("Morocco", arrayListOf()), + Country("Mozambique", arrayListOf()), + Country("Myanmar/Burma", arrayListOf()), + Country("Namibia", arrayListOf()), + Country("Nepal", arrayListOf()), + Country("Netherlands", arrayListOf()), + Country("New Zealand", arrayListOf()), + Country("Nicaragua", arrayListOf()), + Country("Niger", arrayListOf()), + Country("Nigeria", arrayListOf()), + Country("North Macedonia, Republic of", arrayListOf()), + Country("Norway", arrayListOf()), + Country("Oman", arrayListOf()), + Country("Pacific Islands", arrayListOf()), + Country("Pakistan", arrayListOf()), + Country("Panama", arrayListOf()), + Country("Papua New Guinea", arrayListOf()), + Country("Paraguay", arrayListOf()), + Country("Peru", arrayListOf()), + Country("Philippines", arrayListOf()), + Country("Poland", arrayListOf()), + Country("Portugal", arrayListOf()), + Country("Puerto Rico", arrayListOf()), + Country("Qatar", arrayListOf()), + Country("Reunion", arrayListOf()), + Country("Romania", arrayListOf()), + Country("Russian Federation", arrayListOf()), + Country("Rwanda", arrayListOf()), + Country("Saint Kitts and Nevis", arrayListOf()), + Country("Saint Lucia", arrayListOf()), + Country("Saint Vincent and the Grenadines", arrayListOf()), + Country("Samoa", arrayListOf()), + Country("Sao Tome and Principe", arrayListOf()), + Country("Saudi Arabia", arrayListOf()), + Country("Senegal", arrayListOf()), + Country("Serbia", arrayListOf()), + Country("Seychelles", arrayListOf()), + Country("Sierra Leone", arrayListOf()), + Country("Singapore", arrayListOf()), + Country("Slovak Republic (Slovakia)", arrayListOf()), + Country("Slovenia", arrayListOf()), + Country("Solomon Islands", arrayListOf()), + Country("Somalia", arrayListOf()), + Country("South Africa", arrayListOf()), + Country("South Sudan", arrayListOf()), + Country("Spain", arrayListOf(euro)), + Country("Sri Lanka", arrayListOf()), + Country("Sudan", arrayListOf()), + Country("Suriname", arrayListOf()), + Country("Sweden", arrayListOf()), + Country("Switzerland", arrayListOf()), + Country("Syria", arrayListOf()), + Country("Tajikistan", arrayListOf()), + Country("Tanzania", arrayListOf()), + Country("Thailand", arrayListOf()), + Country("Timor Leste", arrayListOf()), + Country("Togo", arrayListOf()), + Country("Trinidad & Tobago", arrayListOf()), + Country("Tunisia", arrayListOf()), + Country("Turkey", arrayListOf()), + Country("Turkmenistan", arrayListOf()), + Country("Turks & Caicos Islands", arrayListOf()), + Country("Uganda", arrayListOf()), + Country("Ukraine", arrayListOf()), + Country("United Arab Emirates", arrayListOf()), + Country("Uruguay", arrayListOf()), + Country("Uzbekistan", arrayListOf()), + Country("Venezuela", arrayListOf()), + Country("Vietnam", arrayListOf()), + Country("Virgin Islands (UK)", arrayListOf()), + Country("Virgin Islands (US)", arrayListOf(usd)), + Country("Yemen", arrayListOf()), + Country("Zambia", arrayListOf()), + Country("Zimbabwe", arrayListOf()) + ) + }*/ + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Currency.kt b/NumismatistAPI/src/items/Currency.kt new file mode 100644 index 0000000..f4422c8 --- /dev/null +++ b/NumismatistAPI/src/items/Currency.kt @@ -0,0 +1,88 @@ +package items + +import kotlin.collections.ArrayList + +class Currency() { + + var name: String + /** + * Unique ID for this item. Provided by the SQL database + */ + var id : Int + var nameAbbr: String + /** + * Currency symbol like $ + */ + var symbol: String + /** + * True if the symbol appears before the value, otherwise false + */ + var symbolBefore: Boolean = true + /** + * Year that the currency started being used. If unknown, use YEAR_START_INVALID + */ + var yrStart : Int + /** + * Year that the currency stopped being used. If unknown, or currency is still in use, use YEAR_END_INVALID + */ + var yrEnd: Int + + init { + name = "" + id = -1 + nameAbbr = "" + symbol = "" + symbolBefore = true + yrStart = YEAR_START_INVALID + yrEnd = YEAR_END_INVALID + } + + constructor(name: String, id : Int, nameAbbr: String, symbol: String, symbolBefore: Boolean = true, yrStart : Int, yrEnd: Int ) : this() { + this.name = name + this.id = id + this.nameAbbr = nameAbbr + this.symbol = symbol + this.symbolBefore = symbolBefore + this.yrStart = yrStart + this.yrEnd = yrEnd + } + + companion object { + + const val YEAR_START_INVALID = 9999 + const val YEAR_END_INVALID = 0 + + /** + * Sorts a list of currencies by name + * + * @param currencies List of currencies to sort + * @return Sorted list of currencies + */ + fun sort(currencies: ArrayList) : ArrayList { + do { + var moves = 0 + for (index in 0 until currencies.size - 1) { + if (currencies[index].yrStart < currencies[index + 1].yrStart) { + val currency = currencies[index] + currencies[index] = currencies[index + 1] + currencies[index + 1] = currency + moves++ + } + } + }while (moves > 0) + + return currencies + } + } + + /** + * Compares 2 currencies to see if they are the same currency. Uses nameAbbr for this comparison + */ + override fun equals(other: Any?) : Boolean { + return other is Currency && other.nameAbbr == this.nameAbbr + } + + override fun hashCode(): Int { + return nameAbbr.hashCode() + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/DatabaseItem.kt b/NumismatistAPI/src/items/DatabaseItem.kt new file mode 100644 index 0000000..80b407d --- /dev/null +++ b/NumismatistAPI/src/items/DatabaseItem.kt @@ -0,0 +1,28 @@ +package items + +import NumismatistAPI + +/** + * DatabaseItems are items that are held and represented in the database. Subclasses include CollectionItem and SetItem + * + * @see CollectionItem + * @see SetItem + */ +abstract class DatabaseItem { + + companion object { + /** + * ID to use for an item that is not in the database + */ + const val ID_INVALID = 0 + } + + /** + * Unique ID for this item. Provided by the SQL database + */ + var id = 0 + + abstract fun saveToDb(api: NumismatistAPI) : Int + + abstract fun removeFromDb(api: NumismatistAPI) : Boolean +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/PageSlot.kt b/NumismatistAPI/src/items/PageSlot.kt new file mode 100644 index 0000000..3a5af0f --- /dev/null +++ b/NumismatistAPI/src/items/PageSlot.kt @@ -0,0 +1,144 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.sql.SQLException + +/** + * A slot in a BookPage, which is in a Book. A slot can hold a single coin. Slots can be of various physical size + * based on the coin that it is designed to hold. A slot can have multiple lines of text below it, which describe the + * coin which the slot is designed for. Slots are generally aligned in rows on the page. + * + * @see Book + * @see BookPage + * @see Coin + */ +class PageSlot() : DatabaseItem() { + + companion object { + const val SIZE_PENNY = 35 + const val SIZE_NICKEL = 40 + const val SIZE_DIME = 30 + const val SIZE_QUARTER = 50 + const val SIZE_HALF = 60 + const val SIZE_DOLLAR = 70 + } + + /** + * The coin that is in this slot. If no coin is in the slot, coin should be null. + */ + var coin : Coin? = null + set(value) { + // Set old coin's slot id to invalid + field?.id = ID_INVALID + + // Set new coin's slot id to this slot + value?.slotId = id + + field = value + } + + /** + * The page which this slot is in + */ + var bookPage = BookPage() + var line1Text = "" + var line2Text = "" + var line3Text = "" + var size = SIZE_DOLLAR + var rowNum = -1 + var colNum = -1 + var denomination = 0.0 + + constructor(size: Int, line1Text: String, line2Text: String) : this() { + this.size = size + this.line1Text = line1Text + this.line2Text = line2Text + } + + /** + * Finds if slot has a coin in it + * + * @return True if slot has a coin in it, otherwise false + */ + fun isSlotFilled() : Boolean { + // If coin is not null, it's filled + return coin != null + } + + fun removeCoin() { + coin = null + } + + /** + * Saves a book page slot to the database. If the slot is not already in the database (id = 0) the slot is added. + * If the slot is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + override fun saveToDb(api: NumismatistAPI) : Int { + val sql : String + + val rows : Int + + if(id != ID_INVALID) { + sql = "UPDATE PageSlots SET PageID=${bookPage.id}, RowNum=${rowNum}, ColNum=${colNum}, " + + "Denomination=${denomination}, Label=\"${line1Text}\", Label2=\"${line2Text}\", " + + "Label3=\"${line3Text}\"\n" + + "WHERE ID=${id};" + + rows = api.runUpdate(sql) + + if(rows == 1 && coin != null) + coin!!.saveToDb(api) + } + else { + sql = "INSERT INTO PageSlots(PageID, RowNum, ColNum, Denomination, Label, Label2, Label3)\n" + + "VALUES(${bookPage.id}, ${rowNum}, ${colNum}, ${denomination}, " + + "\"${line1Text}\", \"${line2Text}\", \"${line3Text}\");" + + rows = api.runUpdate(sql) + + // If successful, set the new ID to this object + if(rows == 1) { + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + + if(coin != null) + // TODO: Handle Errors + coin?.saveToDb(api) + } + } + + return rows + } + + /** + * Removes a book from the database + * + * @param api an API object to use to connect to the database + * + * @return True if book was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + val sql = "DELETE FROM PageSlots WHERE ID=${id}" + + val rows = api.runUpdate(sql) + + api.disconnect() + + if(rows==1) { + // Remove the coin in the slot + // TODO: Handle Error + coin?.removeFromDb(api) + } + + return rows == 1 + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/Set.kt b/NumismatistAPI/src/items/Set.kt new file mode 100644 index 0000000..123a658 --- /dev/null +++ b/NumismatistAPI/src/items/Set.kt @@ -0,0 +1,291 @@ +package items + +import NumismatistAPI +import java.io.FileNotFoundException +import java.io.IOException +import java.sql.SQLException +import kotlin.collections.ArrayList + +class Set : SetItem() { + + companion object { + const val YEAR_NONE = 0 + } + + /** + * A list of items in this set + */ + var items = ArrayList() + + /** + * A list of items that have been removed from this set. List is only valid until the set is saved, + * then it is cleared. + */ + val removedItems = ArrayList() + + /** + * Gets the total face value of all items in the set + * + * @return Total face value of all items in this set + */ + fun getFaceValue() : Double { + var value = 0.0 + + for(item in items) + value += item.denomination + + return value + } + + /** + * Creates a copy of this set, with an id and containerId of ID_INVALID since it will not be in the database, + * and blank image paths + * + * @return A copy of this set + */ + override fun copy() : Set { + val newSet = Set() + + newSet.id = id + newSet.name = name + newSet.value = value + newSet.year = year + newSet.note = note + newSet.set = set + newSet.obvImgPath = obvImgPath + newSet.revImgPath = revImgPath + newSet.containerId = containerId + + // Remove items that are not in this object + for (item in newSet.items) { + if(!items.contains(item)) + newSet.items.remove(item) + } + + // Add all items from this object + for(item in items) { + val newItem = item.copy() + newSet.addItem(newItem) + } + + return newSet + } + + /** + * Adds an item to this set + * + * @param item The item you want to add + */ + fun addItem(item: SetItem) { + item.set = this + + items.add(item) + } + + /** + * Removes an item from the set + * + * @param item The item you want to remove + * + * @return True if the item was in the list + */ + fun removeItem(item: SetItem) : Boolean { + val removed = items.remove(item) + if(removed) + removedItems.add(item) + + return removed + } + + /** + * @return A string in the form of "[ ]" + */ + override fun toString() : String { + var output = "" + + if(year != YEAR_NONE) + output += "$year " + + output += name + + return output + } + + /** + * Saves a set to the database. If the set is not already in the database (id = 0) the set is added. + * If the set is already in the database, it is updated to reflect any changes. + * + * @param api an API object to use to connect to the database + * + * @return How many rows were affected by the sql command + */ + @Throws(IOException::class) + override fun saveToDb(api: NumismatistAPI) : Int { + val sql: String + + // set SetID to null if necessary + val newSetID = if(set == null) + "null" + else + "" + set!!.id + + // set ContainerID to null if necessary + val containerID = if(containerId == ID_INVALID) + "null" + else + "" + containerId + + if(id != 0) { + api.setSet(id, this) + + sql = "UPDATE Sets SET Name=\"${name}\", Yr=${year}, CurValue=${value}, ParentID=$newSetID Note=\"${note}\", " + + "ContainerID=$containerID\n" + + "WHERE ID=${id};" + + var rows = api.runUpdate(sql) + + if(rows == 1) { + var errors = 0 + + // Add items that have been added + for (item in items) { + item.set = this + // Remove from list of individual items + items.remove(item) + + rows = item.saveToDb(api) + if (rows == -1 && errors == 0) { + errors++ + } + + } + + errors = 0 + + for (item in removedItems) { + item.set = null + + if(item.id !=0) { + val itemRows = item.saveToDb(api) + // Add back to list of individual items + items.add(item) + if(itemRows == -1 && errors == 0) { + errors++ + } + } + } + + if(errors > 0) { + throw IOException(NumismatistAPI.getString("error_itemInSetSaveError")) + } + else + return rows + } + else { + api.disconnect() + return rows + } + } + else { + sql = "INSERT INTO Sets(Name, Yr, CurValue, ContainerID, ParentID, Note)\n" + + "VALUES(\"${name}\", ${year}, ${value}, $containerID, $newSetID, \"${note}\");" + + val rows = api.runUpdate(sql) + + // If successful, add items to set + if(rows == 1) { + + // set the new ID to this object + val newID = api.getGeneratedKeys() + if(newID.next()) { + id = Integer.parseInt(newID.getString("GENERATED_KEY")) + } + api.getSets().add(this) + + var errors = 0 + try { + // Add items that have been added + for (item in items) { + item.set = this + + + val itemRows = item.saveToDb(api) + if (itemRows != 1) + errors++ + } + + // Remove items that have been removed + for (item in removedItems) { + item.set = null + + // If items was removed from set, but not from DB + if(item.id !=0) { + val itemRows = item.saveToDb(api) + if (itemRows != 1) + errors++ + } + } + } catch (e: SQLException) { + e.printStackTrace() + } + } + + api.disconnect() + + return rows + } + } + + /** + * Removes a set from the database + * + * @param api an API object to use to connect to the database + * + * @return True if set was successfully removed, otherwise false + */ + @Throws(SQLException::class, FileNotFoundException::class) + override fun removeFromDb(api: NumismatistAPI) : Boolean { + + var itemsInDb = 0 + var removedItems = 0 + + // Remove all items in the set + for (item in items) { + if(item.id != ID_INVALID) { + itemsInDb++ + if (item.removeFromDb(api)) + removedItems++ + } + } + + // If all individual items were removed, remove the set + val rows = if(itemsInDb == removedItems) { + val sql = "DELETE FROM Sets WHERE ID=${id}" + + try { + api.runUpdate(sql) + } + catch (e : SQLException) { + throw e + } + } + else + 0 + + if(rows==1) { + api.getSets().remove(this) + + // Delete pictures + deleteObvImage() + deleteRevImage() + } + + api.disconnect() + + return rows == 1 + } + + fun contains(item : SetItem ) : Boolean { + return this.items.contains(item) + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/items/SetItem.kt b/NumismatistAPI/src/items/SetItem.kt new file mode 100644 index 0000000..f8fcf52 --- /dev/null +++ b/NumismatistAPI/src/items/SetItem.kt @@ -0,0 +1,89 @@ +package items + +import java.util.* + +/** + * SetItems are CollectionItems that can be put in a set, such as Coins, Bills, and Sets + * + * @see CollectionItem + * @see Coin + * @see Bill + * @see Set + */ +@Suppress("unused") +abstract class SetItem : CollectionItem() { + + companion object { + /** + * Default value for year of a coin. If left at this value, the coin is likely a token or medal + */ + const val YEAR_NONE = 0 + + const val NO_DENOMINATION = 0.0 + } + + /** + * The set this item is in. If null then it's not in a set + */ + var set : Set? = null + + var name = "" + var countryName = "" + var currency = Currency() + + /** + * Should be true if the item is professionally graded, otherwise false + */ + var graded = false + var condition = "" + var error = false + var errorType = "" + + /** + * Face value of the item + */ + var denomination = NO_DENOMINATION + set(value) { + if(value >= 0.0) + field = value + } + + /** + * Current approximate sell value of the item + */ + var value = 0.0 + set(value) { + if(value >= 0.0) + field = value + else { + throw NumberFormatException( + ResourceBundle.getBundle("res.strings", Locale.getDefault()) + .getString("error_negativeValue")) + } + } + + /** + * Year on the coin. Can be negative if coin is from BC era, or 0 if no date is on coin (like a token) + */ + var year = YEAR_NONE + + fun addToSet(set: Set) { + set.addItem(this) + } + + abstract fun copy() : SetItem + + /** + * Adds ID to the set this is in. If ID is invalid, does nothing + * + * @param id ID of the set this is in + */ + fun addSetId(id : Int) { + if(id != ID_INVALID) { + if(set == null) + set = Set() + + set!!.id = id + } + } +} \ No newline at end of file diff --git a/NumismatistAPI/src/res/apiStrings_en_US.properties b/NumismatistAPI/src/res/apiStrings_en_US.properties new file mode 100644 index 0000000..0dd7d85 --- /dev/null +++ b/NumismatistAPI/src/res/apiStrings_en_US.properties @@ -0,0 +1,7 @@ +db_message_success=Success! +db_message_noChange=No changes made in database. Something probably went wrong. +db_message_error=Error occurred while updating database. Please try again. +db_message_multipleRows=Updated multiple rows. Make sure that this is what you wanted! + +error_negativeValue=Value cannot be negative +error_itemInSetSaveError=One or more items in this set did not save properly. \ No newline at end of file diff --git a/README.md b/README.md index 1916ebd..bfc6d08 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,110 @@ -# CoinCollection +# Numismatist Helper -This is a program that I designed to document my collection of Coins, Coin Sets, and Dollar Bills. It uses a MySQL database, and a Java/Kotlin program. +This is a program that I designed to document my collection of Coins, Coin Sets, and Banknotes (Dollar Bills.) +It uses a self-hosted MySQL database, and a Java/Kotlin program. -Coins, Bills, and Coin sets can be added to the collection, and then the collection can be viewed in a table format (like Excel.) these tables can also be exported to an xls, xlsx, or csv file for later use. +Coins, banknotes, and sets can be added to the collection, and then the collection can be viewed in a table format (like MS Excel.) +These tables can also be exported to a csv file for later use. -Each item (Coin, Set, or Bill) can also have 2 images attached to it... 1 for the front (obverse) of the item and 1 for the back (reverse) of the item. These images are stored in the user's file system, and the user can decide where ot put these images. Only the file extension is stored in the database. +Each item (Coin, Set, or Banknote) can also have 2 images attached to it... 1 for the front (obverse) of the item and 1 for the back (reverse) of the item. +These images are stored in the user's file system, and the user can decide where to put these images. Only the file extension is stored in the database. -This program was created and tested on a Linux variant based on Ubuntu. I have not done any testing (yet) on other operating systems, but it should work on Windows and probably Mac. +This program is compatible with Windows, Linux, and Mac. However, Mac is untested as I do not own a Mac to test with. It was developed using a combination of +an Ubuntu variant of Linux, and Windows. I will also add screenshots to this ReadMe in the future. -# Database +# WARNING + +This program is still in development, and big changes are likely to be still coming - Including possible changes to the database. +If you are not comfortable with possibly performing some manual changes to your database, it might be best for you to wait a bit before using +this for data that you rely on. + +# Features + +## Multiple countries and currencies + +The database supports multiple countries and their currencies, so regardless of how diverse your collection is, it will still be able to be properly +documented and displayed. You can even add new countries and currencies if something is missing. +(Currently this has to be done at the database level, but future versions will include this functionality in app.) + +Each country can have multiple currencies associated with it, and those currencies also contain the years in which they were used +so that input boxes can change based on information that has already been entered. Each coin and banknote will have a country and currency attached to it. +This is a HUGE job to include all countries and currencies, so it will likely take some time to have a full list. + +**If you're willing to help build this list, it would be much appreciated.** -A diagram of the database setup is included in `Coin Collection.pdf`. This diagram was created using https://dbdiagram.io +## Containers -The database is pretty straight forward, with very little complication involved. +Is your collection spread across multiple boxes or storage containers? No problem! You can add containers and document where each item is. + +# Database + +A diagram of the database setup is included in `Numismatist Helper.pdf`. This diagram was created using https://dbdiagram.io # Installation ## Linux -There is a folder called "linux" with files necessary to create the necessary database and run the program. Run the "CreateDatabase.sh" file and it should install all necessary packages (mariadb-client and mariadb-server), create the database, create a user for the database, and install the necessary JDK-15 to run the program. +There is a folder called "linux" with a script that will install the necessary software, create the database and run the program. + +Run the "CreateDatabase.sh" file, and it should install all necessary packages (mariadb-client and mariadb-server), create the database, create a user for the database, and install the necessary JDK to run the program. -Once the database is created, simply run `CoinCollection.jar` and you should be able to start documenting your collection. +Once the database is created, simply run the command `java -jar NumismatistHelper.jar` and you should be able to start documenting your collection. ## Windows -At this point this has not been tested on Windows. However, if you know how to create a MySQL Server, install a MySQL Client on your PC, and install JDK 15 on your PC, you should be able to run the program. +I have not hosted the database on Windows, however the main program will run in Windows. An exe file is included, which is all you need to run the program. +The exe was built using Launch4j. -The sql file to create the database is included in a folder called "Windows". (It's the same as the file in the "linux" directory.) This file should be all you need to create the database. Once the database is created, you should be able to run the program by running the `CoinCollection.jar` file. +The sql file to create the database is included in a folder called "Windows". This file should be all you need to create the database. Once the database is created, you should be able to run the program by running the `Numismatist Helper.exe` file. -# Future Ideas +# Current Work -Uncut Sheets - In the future I would like to add the ability to also enter uncut sheets of bills. These will be treated in a similar way to Coin Sets. I, personally, do not own any of these sheets at this time, so it was not a priority for me. +I am still actively developing this program. Here are some things I've been working on: -# Building from Source +## Coin Folders -This was originally built using [Intellij Idea](https://www.jetbrains.com/idea/). You will need [mysql-connector-java-*.jar](https://dev.mysql.com/downloads/connector/j/) where * is the current version. This jar is neseccary to connect to the MySQL database. If you do not include this file (or do so improperly) you will receive an error when the program loads, which will tell you to add the file to your project. +Currently, you can store coins, banknotes (dollar bills), and coin sets. A later version will include coin folders. These folders will be customizable, and can be imported using an xml file. (Details to come.) -The program was built using OpenJDK-15, but any JDK of the same (or higher) version level should work fine. +## Android Companion App + +I have started work on an Android companion app, which right now is a read-only app. Next steps for the app is to allow the user to take pictures of items. This is only possible if the pictures directory is pointing to a cloud platform like Google Drive or Dropbox. Because of this, I would also like to implement API access to such services in both the desktop and mobile applications. -The source is a mix of Kotlin and Java. Any file that was generated by Intellij is Java, and the files created by me are Kotlin. This mix of Kotlin and Java may lead to issues if you try to build it with a different tool, but I find Kotlin much easier to both read and write. +# Future Additions / Improvements -## Error Handling +## Table View of Collection -Some of the error handling could admittedly be a little better - mostly returning more precise information. There are a lot of Try/Catch blocks, but some of them do not report the error back in as much detail as it probably should. This is something that will likely be improved in later versions, or if people (other than me) actually start using this. +Right now the only way to view your collection is in a spreadsheet view. I would like to add a table view, which would be customizable as to how the table is +laid out. + +## Spreadsheet View Manipulation + +I would like to add the ability to add and remove columns from the spreadsheet view, so that all information you need is displayed, and nothing more. + +## Uncut Sheets + +In the future I would like to add the ability to also enter uncut sheets of banknotes. These will be treated in a similar way to Coin Sets. I, personally, do not own any of these sheets at this time, so it is not a priority for me. +This can actually be achieved right now by adding each banknote in the sheet to a set. However, this may improve in the future. + +## Cloud Storage Connectivity + +I would like to add out of the box connection to cloud storage providers like Dropbox, Google Drive, and OneDrive to use for storing images. + +## Database Backups + +The ability to create a backup of the database from within the app would be a great feature. + +## Kotlin Multiplatform + +I would like to implement Kotlin Multiplatform when it officially releases (currently in Alpha) so that the desktop and Android apps can have a single code base. + +# Building from Source + +This was originally built using [Intellij Idea](https://www.jetbrains.com/idea/). You will need [mysql-connector-java-5.1.49.jar](https://downloads.mysql.com/archives/c-j/). +This jar is necessary to connect to the MySQL database. +If you do not include this file (or do so improperly) you will receive an error when the program loads, which will tell you to add the file to your project. +The program was built using OpenJDK-15, but any JDK of the same (or higher) version level should work fine. -Also, because of the way that some of the errors are handled, some operations could potentially end up partially done and partially undone. Again, this will likely be addressed in a later version. These scenarios are extremely unlikely, but should stil be addressed in the rare case that one of those extremely unlikey scenarios occurs. +The source is a mix of Kotlin and Java. Any file that was generated by Intellij is Java, and the files created by me are Kotlin. +This mix of Kotlin and Java may lead to issues if you try to build it with a different tool, but I find Kotlin much easier to both read and write. diff --git a/Windows/create.sql b/Windows/create.sql index c016b7c..a5ed71b 100644 --- a/Windows/create.sql +++ b/Windows/create.sql @@ -1,88 +1,406 @@ --- MySQL dump 10.16 Distrib 10.1.44-MariaDB, for debian-linux-gnu (x86_64) +-- phpMyAdmin SQL Dump +-- version 4.9.7 +-- https://www.phpmyadmin.net/ -- --- Host: localhost Database: CoinCollection --- ------------------------------------------------------ --- Server version 10.1.44-MariaDB-0ubuntu0.18.04.1 +-- Host: localhost +-- Generation Time: Oct 10, 2021 at 09:35 AM +-- Server version: 10.3.29-MariaDB +-- PHP Version: 7.3.24 -CREATE DATABASE CoinCollection; -USE CoinCollection; +SET FOREIGN_KEY_CHECKS=0; +-- SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +-- SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `CoinCollection` +-- +CREATE DATABASE IF NOT EXISTS `CoinCollection` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +USE `CoinCollection`; + +-- -------------------------------------------------------- -- -- Table structure for table `Bills` -- -DROP TABLE IF EXISTS `Bills`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `Bills` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'United States', + `ID` bigint(20) UNSIGNED NOT NULL, + `CountryName` varchar(100) NOT NULL, + `CurrencyAbbr` varchar(10) NOT NULL, `Type` varchar(100) DEFAULT NULL, `Yr` int(11) NOT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, + `SeriesLetter` varchar(3) DEFAULT NULL, + `Serial` varchar(20) DEFAULT NULL, + `Denomination` decimal(20,3) UNSIGNED NOT NULL, + `CurValue` decimal(15,2) UNSIGNED DEFAULT NULL, + `Graded` tinyint(1) DEFAULT NULL, `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', + `Error` tinyint(1) DEFAULT 0, `ErrorType` varchar(100) DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `PlateSeriesObv` varchar(10) DEFAULT NULL, - `PlateSeriesRev` varchar(10) DEFAULT NULL, - `Star` tinyint(1) DEFAULT NULL, - `NotePosition` varchar(10) DEFAULT NULL, - `District` varchar(5) DEFAULT NULL, + `Replacement` tinyint(1) DEFAULT 0, + `Signatures` varchar(255) DEFAULT NULL, `ObvImgExt` varchar(10) DEFAULT NULL, `RevImgExt` varchar(10) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT NULL, - `Signatures` varchar(255) DEFAULT NULL, - `SeriesLetter` varchar(3) DEFAULT NULL, - PRIMARY KEY (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; + `Note` varchar(1024) DEFAULT NULL, + `ContainerID` bigint(20) UNSIGNED DEFAULT NULL, + `SetID` bigint(20) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- -- --- Table structure for table `Sets` +-- Table structure for table `BookPages` -- -DROP TABLE IF EXISTS `Sets`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `Sets` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Name` varchar(100) DEFAULT NULL, - `Yr` int(11) DEFAULT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; +CREATE TABLE `BookPages` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `BookID` bigint(20) UNSIGNED NOT NULL, + `PageNum` int(10) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Books` +-- + +CREATE TABLE `Books` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `Title` varchar(64) NOT NULL, + `Denomination` decimal(20,3) UNSIGNED NOT NULL, + `StartYear` int(10) UNSIGNED NOT NULL, + `EndYear` int(10) UNSIGNED NOT NULL, + `ContainerID` bigint(20) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- -- -- Table structure for table `Coins` -- -DROP TABLE IF EXISTS `Coins`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `Coins` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'United States', + `ID` bigint(20) UNSIGNED NOT NULL, + `CountryName` varchar(100) NOT NULL, + `CurrencyAbbr` varchar(10) NOT NULL, `Type` varchar(100) DEFAULT NULL, `Yr` int(11) NOT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `MintMark` enum('C','CC','D','O','P','S','W') DEFAULT NULL, + `Denomination` decimal(20,3) UNSIGNED NOT NULL, + `CurValue` decimal(20,2) UNSIGNED DEFAULT NULL, + `MintMark` varchar(3) DEFAULT NULL, + `Graded` tinyint(1) DEFAULT 0, `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', + `Error` tinyint(1) DEFAULT 0, `ErrorType` varchar(100) DEFAULT NULL, - `SetID` bigint(20) unsigned DEFAULT NULL, + `SetID` bigint(20) UNSIGNED DEFAULT NULL CHECK (`SetID` is null or `SlotID` is null), + `SlotID` bigint(20) UNSIGNED DEFAULT NULL, + `ObvImgExt` varchar(10) DEFAULT NULL, + `RevImgExt` varchar(10) DEFAULT NULL, `Note` varchar(1024) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT '0', + `ContainerID` bigint(20) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Containers` +-- + +CREATE TABLE `Containers` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `Name` varchar(100) NOT NULL, + `ParentID` bigint(20) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Countries` +-- + +CREATE TABLE `Countries` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `Name` varchar(100) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `CountryCurrencies` +-- + +CREATE TABLE `CountryCurrencies` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `CountryName` varchar(100) NOT NULL, + `CurrencyAbbr` varchar(10) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Currencies` +-- + +CREATE TABLE `Currencies` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `Name` varchar(100) DEFAULT NULL, + `Abbreviation` varchar(10) DEFAULT NULL, + `Symbol` varchar(10) DEFAULT NULL, + `SymbolBefore` tinyint(1) DEFAULT 1, + `YrStart` int(10) UNSIGNED NOT NULL DEFAULT 9999, + `YrEnd` int(10) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `PageSlots` +-- + +CREATE TABLE `PageSlots` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `PageID` bigint(20) UNSIGNED NOT NULL, + `RowNum` int(10) UNSIGNED NOT NULL, + `ColNum` int(10) UNSIGNED NOT NULL, + `Denomination` decimal(20,3) UNSIGNED DEFAULT NULL, + `Label` varchar(50) DEFAULT NULL, + `Label2` varchar(50) DEFAULT NULL, + `Label3` varchar(50) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Sets` +-- + +CREATE TABLE `Sets` ( + `ID` bigint(20) UNSIGNED NOT NULL, + `Name` varchar(100) DEFAULT NULL, + `Yr` int(11) DEFAULT NULL, + `CurValue` decimal(15,2) UNSIGNED DEFAULT NULL, `ObvImgExt` varchar(10) DEFAULT NULL, `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`), - KEY `SetID` (`SetID`), - CONSTRAINT `Coins_ibfk_1` FOREIGN KEY (`SetID`) REFERENCES `Sets` (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; \ No newline at end of file + `Note` varchar(1024) DEFAULT NULL, + `ContainerID` bigint(20) UNSIGNED DEFAULT NULL, + `ParentID` bigint(20) UNSIGNED DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `Bills` +-- +ALTER TABLE `Bills` + ADD PRIMARY KEY (`ID`), + ADD KEY `CountryName` (`CountryName`), + ADD KEY `CurrencyAbbr` (`CurrencyAbbr`), + ADD KEY `ContainerID` (`ContainerID`), + ADD KEY `SetID` (`SetID`); + +-- +-- Indexes for table `BookPages` +-- +ALTER TABLE `BookPages` + ADD PRIMARY KEY (`ID`), + ADD KEY `BookID` (`BookID`); + +-- +-- Indexes for table `Books` +-- +ALTER TABLE `Books` + ADD PRIMARY KEY (`ID`), + ADD KEY `ContainerID` (`ContainerID`); + +-- +-- Indexes for table `Coins` +-- +ALTER TABLE `Coins` + ADD PRIMARY KEY (`ID`), + ADD KEY `SetID` (`SetID`), + ADD KEY `SlotID` (`SlotID`), + ADD KEY `CountryName` (`CountryName`), + ADD KEY `CurrencyAbbr` (`CurrencyAbbr`), + ADD KEY `ContainerID` (`ContainerID`); + +-- +-- Indexes for table `Containers` +-- +ALTER TABLE `Containers` + ADD PRIMARY KEY (`ID`), + ADD UNIQUE KEY `Name` (`Name`), + ADD KEY `ParentID` (`ParentID`); + +-- +-- Indexes for table `Countries` +-- +ALTER TABLE `Countries` + ADD PRIMARY KEY (`ID`), + ADD UNIQUE KEY `Name` (`Name`); + +-- +-- Indexes for table `CountryCurrencies` +-- +ALTER TABLE `CountryCurrencies` + ADD PRIMARY KEY (`ID`), + ADD KEY `CountryName` (`CountryName`), + ADD KEY `CurrencyAbbr` (`CurrencyAbbr`); + +-- +-- Indexes for table `Currencies` +-- +ALTER TABLE `Currencies` + ADD PRIMARY KEY (`ID`), + ADD UNIQUE KEY `Abbreviation` (`Abbreviation`); + +-- +-- Indexes for table `PageSlots` +-- +ALTER TABLE `PageSlots` + ADD PRIMARY KEY (`ID`), + ADD KEY `PageID` (`PageID`); + +-- +-- Indexes for table `Sets` +-- +ALTER TABLE `Sets` + ADD PRIMARY KEY (`ID`), + ADD KEY `ContainerID` (`ContainerID`), + ADD KEY `ParentID` (`ParentID`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `Bills` +-- +ALTER TABLE `Bills` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `BookPages` +-- +ALTER TABLE `BookPages` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Books` +-- +ALTER TABLE `Books` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Coins` +-- +ALTER TABLE `Coins` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Containers` +-- +ALTER TABLE `Containers` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Countries` +-- +ALTER TABLE `Countries` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `CountryCurrencies` +-- +ALTER TABLE `CountryCurrencies` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Currencies` +-- +ALTER TABLE `Currencies` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `PageSlots` +-- +ALTER TABLE `PageSlots` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `Sets` +-- +ALTER TABLE `Sets` + MODIFY `ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `Bills` +-- +ALTER TABLE `Bills` + ADD CONSTRAINT `Bills_ibfk_1` FOREIGN KEY (`CountryName`) REFERENCES `Countries` (`Name`), + ADD CONSTRAINT `Bills_ibfk_2` FOREIGN KEY (`CurrencyAbbr`) REFERENCES `Currencies` (`Abbreviation`), + ADD CONSTRAINT `Bills_ibfk_3` FOREIGN KEY (`ContainerID`) REFERENCES `Containers` (`ID`), + ADD CONSTRAINT `Bills_ibfk_4` FOREIGN KEY (`SetID`) REFERENCES `Sets` (`ID`); + +-- +-- Constraints for table `BookPages` +-- +ALTER TABLE `BookPages` + ADD CONSTRAINT `BookPages_ibfk_1` FOREIGN KEY (`BookID`) REFERENCES `Books` (`ID`); + +-- +-- Constraints for table `Books` +-- +ALTER TABLE `Books` + ADD CONSTRAINT `Books_ibfk_1` FOREIGN KEY (`ContainerID`) REFERENCES `Containers` (`ID`); + +-- +-- Constraints for table `Coins` +-- +ALTER TABLE `Coins` + ADD CONSTRAINT `Coins_ibfk_1` FOREIGN KEY (`SetID`) REFERENCES `Sets` (`ID`), + ADD CONSTRAINT `Coins_ibfk_6` FOREIGN KEY (`CountryName`) REFERENCES `Countries` (`Name`), + ADD CONSTRAINT `Coins_ibfk_7` FOREIGN KEY (`CurrencyAbbr`) REFERENCES `Currencies` (`Abbreviation`), + ADD CONSTRAINT `Coins_ibfk_8` FOREIGN KEY (`ContainerID`) REFERENCES `Containers` (`ID`); + +-- +-- Constraints for table `Containers` +-- +ALTER TABLE `Containers` + ADD CONSTRAINT `Containers_ibfk_1` FOREIGN KEY (`ParentID`) REFERENCES `Containers` (`ID`); + +-- +-- Constraints for table `CountryCurrencies` +-- +ALTER TABLE `CountryCurrencies` + ADD CONSTRAINT `CountryCurrencies_ibfk_1` FOREIGN KEY (`CountryName`) REFERENCES `Countries` (`Name`), + ADD CONSTRAINT `CountryCurrencies_ibfk_2` FOREIGN KEY (`CurrencyAbbr`) REFERENCES `Currencies` (`Abbreviation`); + +-- +-- Constraints for table `PageSlots` +-- +ALTER TABLE `PageSlots` + ADD CONSTRAINT `PageSlots_ibfk_1` FOREIGN KEY (`PageID`) REFERENCES `BookPages` (`ID`); + +-- +-- Constraints for table `Sets` +-- +ALTER TABLE `Sets` + ADD CONSTRAINT `Sets_ibfk_1` FOREIGN KEY (`ContainerID`) REFERENCES `Containers` (`ID`), + ADD CONSTRAINT `Sets_ibfk_2` FOREIGN KEY (`ParentID`) REFERENCES `Sets` (`ID`); +SET FOREIGN_KEY_CHECKS=1; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..9048e51 Binary files /dev/null and b/icon.ico differ diff --git a/images/District.jpg b/images/District.jpg deleted file mode 100644 index f0947fd..0000000 Binary files a/images/District.jpg and /dev/null differ diff --git a/images/Note_Position.jpg b/images/Note_Position.jpg deleted file mode 100644 index 734c69e..0000000 Binary files a/images/Note_Position.jpg and /dev/null differ diff --git a/images/Plate_Series_obv.jpg b/images/Plate_Series_obv.jpg deleted file mode 100644 index eee8782..0000000 Binary files a/images/Plate_Series_obv.jpg and /dev/null differ diff --git a/images/Plate_Series_rev.jpg b/images/Plate_Series_rev.jpg deleted file mode 100644 index 1fe7b3f..0000000 Binary files a/images/Plate_Series_rev.jpg and /dev/null differ diff --git a/lib/mysql-connector-java-5.1.49.jar b/lib/mysql-connector-java-5.1.49.jar new file mode 100644 index 0000000..d3c8b41 Binary files /dev/null and b/lib/mysql-connector-java-5.1.49.jar differ diff --git a/lib/tornadofx-1.7.20.jar b/lib/tornadofx-1.7.20.jar new file mode 100644 index 0000000..c6bf77b Binary files /dev/null and b/lib/tornadofx-1.7.20.jar differ diff --git a/linux/CreateDatabase.sh b/linux/CreateDatabase.sh old mode 100755 new mode 100644 index 48a129b..8a14b0b --- a/linux/CreateDatabase.sh +++ b/linux/CreateDatabase.sh @@ -37,85 +37,147 @@ then echo "" fi + +if [ 1 -eq 0 ]; then username="coins" -password="coinDatabasePassword" +password="coinDatabasePassword1!" databaseName="CoinCollection" # Create database and start using it +echo "Creating database..." +echo "" sudo mysql -e "CREATE DATABASE $databaseName;" sudo mysql -e "USE $databaseName;" -# Create bills table -sudo mysql -e "DROP TABLE IF EXISTS `Bills`;" -sudo mysql -e "CREATE TABLE `Bills` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'US', - `Type` varchar(100) DEFAULT NULL, - `Yr` int(11) NOT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', - `ErrorType` varchar(100) DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `PlateSeriesObv` varchar(10) DEFAULT NULL, - `PlateSeriesRev` varchar(10) DEFAULT NULL, - `Star` tinyint(1) DEFAULT NULL, - `NotePosition` varchar(10) DEFAULT NULL, - `District` varchar(5) DEFAULT NULL, - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT NULL, - `Signatures` varchar(255) DEFAULT NULL, - `SeriesLetter` varchar(3) DEFAULT NULL, - PRIMARY KEY (`ID`) +# Create countries table +sudo mysql -e "DROP TABLE IF EXISTS `Countries`;" +sudo mysql -e "CREATE TABLE Countries ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + Name varchar(100) UNIQUE + );" + +# Create currencies table +sudo mysql -e "DROP TABLE IF EXISTS `Currencies`;" +sudo mysql -e "CREATE TABLE Currencies ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + Name varchar(100) NOT NULL, + Abbreviation varchar(10) UNIQUE, + Symbol varchar(10), + SymbolBefore boolean default true, + YrStart int UNSIGNED NOT NULL default 9999, + YrEnd int UNSIGNED default NULL + );" + +# Create countryCurrencies table +sudo mysql -e "DROP TABLE IF EXISTS `CountryCurrencies`;" +sudo mysql -e "CREATE TABLE CountryCurrencies ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + CountryName varchar(100) NOT NULL, + CurrencyAbbr varchar(10) NOT NULL, + FOREIGN KEY(CountryName) REFERENCES Countries(Name), + FOREIGN KEY(CurrencyAbbr) REFERENCES Currencies(Abbreviation) + );" + +# Create containers table +sudo mysql -e "DROP TABLE IF EXISTS `Containers`;" +sudo mysql -e "CREATE TABLE Containers ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + Name varchar(100) UNIQUE NOT NULL, + ParentID bigint UNSIGNED, + FOREIGN KEY(ParentID) REFERENCES Containers(ID) );" # Create Sets table sudo mysql -e "DROP TABLE IF EXISTS `Sets`;" -sudo mysql -e "CREATE TABLE `Sets` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Name` varchar(100) DEFAULT NULL, - `Yr` int(11) DEFAULT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`) +sudo mysql -e "CREATE TABLE Sets ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + Name varchar(100), + Yr int, + CurValue Decimal(20,2) UNSIGNED, + ObvImgExt varchar(10), + RevImgExt varchar(10), + Note varchar(1024), + ContainerID bigint UNSIGNED, + ParentID bigint UNSIGNED, + FOREIGN KEY(ParentID) REFERENCES Sets(ID), + FOREIGN KEY(ContainerID) REFERENCES Containers(ID) );" # Create Coins table sudo mysql -e "DROP TABLE IF EXISTS `Coins`;" -sudo mysql -e "CREATE TABLE `Coins` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'US', - `Type` varchar(100) DEFAULT NULL, - `Yr` int(11) NOT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `MintMark` enum('C','CC','D','O','P','S','W') DEFAULT NULL, - `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', - `ErrorType` varchar(100) DEFAULT NULL, - `SetID` bigint(20) unsigned DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT '0', - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`), - KEY `SetID` (`SetID`), - CONSTRAINT `Coins_ibfk_1` FOREIGN KEY (`SetID`) REFERENCES `Sets` (`ID`) +sudo mysql -e "CREATE TABLE Coins ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + CountryName varchar(100) NOT NULL, + CurrencyAbbr varchar(10) NOT NULL, + Type varchar(100), + Yr int NOT NULL, + Denomination Decimal(20,3) UNSIGNED NOT NULL, + CurValue Decimal(20,2) UNSIGNED, + MintMark varchar(3), + Graded Boolean default false, + Grade varchar(10), + Error boolean default false, + ErrorType varchar(100), + SetID bigint UNSIGNED default NULL + check (SetID is null or SlotID is null), + SlotID bigint UNSIGNED default NULL + check (SetID is null or SlotID is null), + ObvImgExt varchar(10), + RevImgExt varchar(10), + Note varchar(1024), + ContainerID bigint UNSIGNED, + FOREIGN KEY(ContainerID) REFERENCES Containers(ID), + FOREIGN KEY(SetID) REFERENCES Sets(ID), + FOREIGN KEY(SlotID) REFERENCES RowSlots(ID), + FOREIGN KEY(CountryName) REFERENCES Countries(Name), + FOREIGN KEY(CurrencyAbbr) REFERENCES Currencies(Abbreviation) +);" + +# Create bills table +sudo mysql -e "DROP TABLE IF EXISTS `Bills`;" +sudo mysql -e "CREATE TABLE Bills ( + ID bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT, + CountryName varchar(100) NOT NULL, + CurrencyAbbr varchar(10) NOT NULL, + Type varchar(100), + Yr int NOT NULL, + SeriesLetter varchar(3), + Serial varchar(20), + Denomination Decimal(20,3) UNSIGNED NOT NULL, + CurValue Decimal(20,2) UNSIGNED, + Graded boolean, + Grade varchar(10), + Error boolean default false, + ErrorType varchar(100), + Replacement boolean default false, + Signatures varchar(255), + ObvImgExt varchar(10), + RevImgExt varchar(10), + Note varchar(1024), + ContainerID bigint UNSIGNED, + SetID bigint UNSIGNED, + FOREIGN KEY(SetID) REFERENCES Sets(ID), + FOREIGN KEY(ContainerID) REFERENCES Containers(ID), + FOREIGN KEY(CountryName) REFERENCES Countries(Name), + FOREIGN KEY(CurrencyAbbr) REFERENCES Currencies(Abbreviation) );" # Create user and give permissons to database -sudo mysql -e "CREATE USER $username@localhost IDENTIFIED BY \"$password\";" -sudo mysql -e "GRANT ALL ON $databaseName.* TO $username@localhost IDENTIFIED BY \"$password\";" +# Use % to allow remote access +echo "Creating SQL user and granting permissions..." +echo "" +sudo mysql -e "CREATE USER \"$username\"@\"%\" IDENTIFIED BY \"$password\";" +sudo mysql -e "GRANT ALL ON $databaseName.* TO \"$username\"@\"%\" IDENTIFIED BY \"$password\";" # Apply new permissions sudo mysql -e "FLUSH PRIVILEGES;" +fi -echo "Installing JDK 15..." +echo "Installing JDK 16..." +echo "" -# Install Jdk 15 +# Install Jdk 16 sudo add-apt-repository ppa:linuxuprising/java sudo apt update -sudo apt install oracle-java15-installer -y \ No newline at end of file +sudo apt install oracle-java16-set-default -y + +echo "Done!" \ No newline at end of file diff --git a/linux/create.sql b/linux/create.sql deleted file mode 100644 index ea8f655..0000000 --- a/linux/create.sql +++ /dev/null @@ -1,89 +0,0 @@ --- MySQL dump 10.16 Distrib 10.1.44-MariaDB, for debian-linux-gnu (x86_64) --- --- Host: localhost Database: CoinCollection --- ------------------------------------------------------ --- Server version 10.1.44-MariaDB-0ubuntu0.18.04.1 - -CREATE DATABASE CoinCollection; -USE CoinCollection; - --- --- Table structure for table `Bills` --- - -DROP TABLE IF EXISTS `Bills`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `Bills` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'United States', - `Type` varchar(100) DEFAULT NULL, - `Yr` int(11) NOT NULL, - `SeriesLetter` varchar(3) DEFAULT NULL, - `Serial` varchar(32) DEFAULT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', - `ErrorType` varchar(100) DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `PlateSeriesObv` varchar(10) DEFAULT NULL, - `PlateSeriesRev` varchar(10) DEFAULT NULL, - `Star` tinyint(1) DEFAULT NULL, - `NotePosition` varchar(10) DEFAULT NULL, - `District` varchar(5) DEFAULT NULL, - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT NULL, - `Signatures` varchar(255) DEFAULT NULL, - PRIMARY KEY (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `Sets` --- - -DROP TABLE IF EXISTS `Sets`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `Sets` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Name` varchar(100) DEFAULT NULL, - `Yr` int(11) DEFAULT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `Coins` --- - -DROP TABLE IF EXISTS `Coins`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `Coins` ( - `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `Country` varchar(32) NOT NULL DEFAULT 'United States', - `Type` varchar(100) DEFAULT NULL, - `Yr` int(11) NOT NULL, - `Denomination` decimal(7,2) unsigned NOT NULL, - `CurValue` decimal(15,2) unsigned DEFAULT NULL, - `MintMark` enum('', 'C','CC','D','O','P','S','W') DEFAULT NULL, - `Grade` varchar(10) DEFAULT NULL, - `Error` tinyint(1) DEFAULT '0', - `ErrorType` varchar(100) DEFAULT NULL, - `SetID` bigint(20) unsigned DEFAULT NULL, - `Note` varchar(1024) DEFAULT NULL, - `Graded` tinyint(1) DEFAULT '0', - `ObvImgExt` varchar(10) DEFAULT NULL, - `RevImgExt` varchar(10) DEFAULT NULL, - PRIMARY KEY (`ID`), - KEY `SetID` (`SetID`), - CONSTRAINT `Coins_ibfk_1` FOREIGN KEY (`SetID`) REFERENCES `Sets` (`ID`) -) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; \ No newline at end of file diff --git a/src/AboutScreen.form b/src/AboutScreen.form index 19f8d79..96f9468 100644 --- a/src/AboutScreen.form +++ b/src/AboutScreen.form @@ -8,7 +8,7 @@ - + @@ -16,30 +16,12 @@ - - - - - - - - - - - - - - - - - - - + @@ -47,6 +29,22 @@ + + + + + + + + + + + + + + + + @@ -57,12 +55,13 @@ - + - + + @@ -78,6 +77,7 @@ + diff --git a/src/AboutScreen.java b/src/AboutScreen.java index 020c4f3..35fd23f 100644 --- a/src/AboutScreen.java +++ b/src/AboutScreen.java @@ -1,21 +1,30 @@ import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; +import java.io.File; +import java.net.URISyntaxException; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.util.Calendar; public class AboutScreen extends JDialog { private JPanel contentPane; private JButton buttonOK; private JTextPane descriptionPane; private JLabel versionLabel; + private JLabel buildLabel; + private JLabel titleDisplay; public AboutScreen(JFrame parent) { super(parent); setContentPane(contentPane); setModal(true); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); setMinimumSize(new Dimension(400, 300)); - setTitle("About"); + setTitle(Main.getString("aboutScreen_title")); setResizable(false); getRootPane().setDefaultButton(buttonOK); + setIconImage(Main.getIcon().getImage()); // close on ESCAPE contentPane.registerKeyboardAction(e -> dispose(), @@ -24,12 +33,30 @@ public AboutScreen(JFrame parent) { buttonOK.addActionListener(e -> onOK()); - descriptionPane.setText("This program was designed to document and track a collection of coins, " + - "coin sets, and paper money.\n" + - "\nDesigned and written by Randy Havens of Fifteen 15 Studios.\n" + - "\nCopyright 2020"); + String buildDate = ""; + Font titleFont = titleDisplay.getFont(); + titleDisplay.setFont(new Font(titleFont.getName(), Font.BOLD, titleFont.getSize())); - versionLabel.setText("Version: " + Main.version); + try { + File jarFile = new File + (this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + DateFormat dateFormat = DateFormat.getDateInstance(); + buildDate += dateFormat.format(jarFile.lastModified()); + } catch (URISyntaxException e1) { + //ignore + } + + int year = Calendar.getInstance().get(Calendar.YEAR); + + descriptionPane.setText(MessageFormat.format(Main.getString("aboutScreen_message"), "" + year)); + + String version = MessageFormat.format(Main.getString("aboutScreen_label_version"), ((Main) parent).getVersion()); + versionLabel.setText(version); + + if(!buildDate.isBlank()) { + String buildString = MessageFormat.format(Main.getString("aboutScreen_label_build"), buildDate); + buildLabel.setText(buildString); + } } private void onOK() { diff --git a/src/AddBillScreen.form b/src/AddBillScreen.form index b7192ba..f0c4dc5 100644 --- a/src/AddBillScreen.form +++ b/src/AddBillScreen.form @@ -1,490 +1,500 @@
- + - + - - - - - - - - - + - - + + - - - - - - - - - - + + + + + + + + + + - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - + - - - + - + - + - + + + + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + - + - + - - - + - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + + - + - + - + + - + - - + + - + - + - - - + - + - - + + - + - + + - - + + - + - + - + + - + - - - + - + - + - + - + + - + + + + + + + + + - + - + + - + - + - + - + - + + - - - - - - - - - - - + - + + + - - - - + + - + - + - + - + - - - - - - - - - + - - - + - + + - + - - - - - - - - - + + + - - - + - + - + - + - + - + - + - - - - - - - - - - - + - + + - + - - - - - - - - - + + + - + - + - + - + + - + - + + + - + - - - - - - - - - + - + - + + - - - - - - - - - + - - - + - + - + - + - - - + - + - + - + + - + - + + - - - - - - - - - + + + + diff --git a/src/AddBillScreen.java b/src/AddBillScreen.java index 8a8bf6b..9783ca9 100644 --- a/src/AddBillScreen.java +++ b/src/AddBillScreen.java @@ -1,24 +1,24 @@ -import items.Bill; -import items.DatabaseConnection; +import items.*; +import org.jetbrains.annotations.NotNull; import javax.imageio.ImageIO; import javax.swing.*; +import javax.swing.text.PlainDocument; import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.ArrayList; -public class AddBillScreen { +public class AddBillScreen extends AddSetItemScreen { private JTextField typeInput; private JButton cancelButton; private JButton OKButton; - private JTextField countryInput; private JTextField yearInput; private JTextField denominationInput; - private JComboBox gradeComboBox; + private AutoComboBox gradeComboBox; private JCheckBox gradedCheckBox; private JCheckBox errorCheckBox; private JTextField errorTypeInput; @@ -33,76 +33,85 @@ public class AddBillScreen { private JButton revSetButton; private JButton revBrowseButton; private JButton revRemoveButton; - private JTextField plateSeriesObvInput; - private JTextField plateSeriesRevInput; - private JTextField districtInput; - private JTextField notePositionInput; private JTextArea errorDisplay; - private JLabel fieldTips; private JCheckBox starCheck; private JTextField valueInput; - private JButton addAnotherButton; + private JButton saveNewButton; private JTextField signaturesInput; private JPanel obvPicPanel; private JPanel revPicPanel; private JTextField seriesLetter; - - private final JFrame parent; + private AutoComboBox countryInput; + private AutoComboBox currencyInput; + private JButton saveCopyButton; + private JComboBox locationDropDown; + private JButton addContainerButton; + private JScrollPane scrollPane; private Bill bill; - private String obvImageLocation = ""; - private String revImageLocation = ""; + boolean editingSet = false; + + public AddBillScreen(JFrame parent) { + this(parent, new Bill()); + } + + public AddBillScreen(JFrame parent, final Bill bill) { + super(parent); - private boolean validObvImg = false; - private boolean validRevImg = false; + setReturnTab(TAB_BILLS); - private boolean fromCollection = false; + setImageObvLocationInput(imageObvLocationInput); + setImageRevLocationInput(imageRevLocationInput); - public AddBillScreen(JFrame parent) { - this.parent = parent; - - setBill(new Bill()); - - fieldTips.setText("What are these?"); - fieldTips.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - fieldTips.addMouseListener(new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - // Show bill info - BillInfoDialog infoDialog = new BillInfoDialog(parent); - infoDialog.setVisible(true); + setObvPicPanel(obvPicPanel); + setRevPicPanel(revPicPanel); + + api = ((Main) parent).api; + + api.setCountryListener(new NumismatistAPI.CountryListener() { + @Override + public void countryListChanged(@NotNull ArrayList countries) { + //Update Country and Currency lists without changing selection + Object curCountry = countryInput.getSelectedItem(); + Object curCurrency = currencyInput.getSelectedItem(); + ComboBoxHelper.setupBoxes(countryInput, yearInput, currencyInput, errorDisplay, api); + if(curCountry != null && !curCountry.equals("")) + countryInput.setSelectedItem(curCountry); + if(curCurrency != null && !curCurrency.equals("")) + currencyInput.setSelectedItem(curCurrency); } }); + this.bill = bill; + errorCheckBox.addActionListener(e -> errorTypeInput.setEnabled(errorCheckBox.isSelected())); cancelButton.addActionListener(e -> goHome()); - addAnotherButton.addActionListener( e -> { - if(saveBill()) { - errorDisplay.setForeground(Color.GREEN); - errorDisplay.setText("Bill saved!"); + ComboBoxHelper.setContainerList(locationDropDown, api, addContainerButton, parent); + ComboBoxHelper.setupBoxes( countryInput, yearInput, currencyInput, errorDisplay, api); - setBill(new Bill()); + // Restrict input + ((PlainDocument) denominationInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getDenominationFilter()); + ((PlainDocument) yearInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getCurrentYearFilter()); + ((PlainDocument) valueInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getValueFilter()); - parent.setTitle("Add Bill"); - } - }); + MyLetterFilter letterFilter = new MyLetterFilter(); + letterFilter.setMaxLetters(1); + ((PlainDocument) seriesLetter.getDocument()).setDocumentFilter(letterFilter); - OKButton.addActionListener( e-> { - if(saveBill()) - goHome(); - }); + saveNewButton.addActionListener(e -> saveBill(getBUTTON_SAVE_NEW())); + saveCopyButton.addActionListener(e -> saveBill(getBUTTON_SAVE_COPY())); + OKButton.addActionListener( e-> saveBill(getBUTTON_OK())); obvSetButton.addActionListener(e -> { - obvImageLocation = imageObvLocationInput.getText(); - addImage(obvImageLocation, true); + setObvImageLocation(imageObvLocationInput.getText()); + addImage(getObvImageLocation(), true); }); revSetButton.addActionListener(e -> { - revImageLocation = imageRevLocationInput.getText(); - addImage(revImageLocation, false); + setRevImageLocation(imageRevLocationInput.getText()); + addImage(getRevImageLocation(), false); }); obvBrowseButton.addActionListener( e -> openFileChooser(true)); @@ -114,15 +123,41 @@ public void mouseClicked(MouseEvent e) for(int i = 0; i < Bill.Companion.getCONDITIONS().length; i++) { gradeComboBox.addItem(Bill.Companion.getCONDITIONS()[i]); } + parent.getRootPane().setDefaultButton(OKButton); + // Set initial focus + SwingUtilities.invokeLater(() -> countryInput.requestFocus()); + + setInfo(); } private void setInfo() { typeInput.setText(bill.getName()); - countryInput.setText(bill.getCountry()); + String countryName = Country.Companion.getCountry(api.getCountries(), bill.getCountryName()).getName(); + + if(!countryName.isBlank()) + ((JTextField)countryInput.getEditor().getEditorComponent()).setText(countryName); + if(bill.getYear() != 0) yearInput.setText("" + bill.getYear()); else yearInput.setText(""); + + // Must happen after country and year are set + try { + ComboBoxHelper.setCurrency( countryInput, yearInput, currencyInput, api.getCountries()); + } + catch (NumberFormatException nfe) { + errorDisplay.setText(Main.getString("error_invalidDate")); + } + + if(!bill.getCurrency().getNameAbbr().equals("")) { + + ((JTextField) currencyInput.getEditor().getEditorComponent()).setText( + api.findCurrency(bill.getCurrency().getNameAbbr()).getName()); + } + + locationDropDown.setSelectedItem(api.findContainer(bill.getContainerId()).getName()); + serialNumberInput.setText(bill.getSerial()); if(bill.getDenomination() != 0.0) { // Format the number properly @@ -146,37 +181,49 @@ private void setInfo() { else valueInput.setText(""); - plateSeriesObvInput.setText(bill.getPlateSeriesObv()); - plateSeriesRevInput.setText(bill.getPlateSeriesRev()); - districtInput.setText(bill.getDistrict()); - notePositionInput.setText(bill.getNotePosition()); gradedCheckBox.setSelected(bill.getGraded()); gradeComboBox.setSelectedItem(bill.getCondition()); errorCheckBox.setSelected(bill.getError()); errorTypeInput.setText(bill.getErrorType()); seriesLetter.setText(bill.getSeriesLetter()); - starCheck.setSelected(bill.getStar()); + starCheck.setSelected(bill.getReplacement()); noteInput.setText(bill.getNote()); // Set images - if(!bill.getObvImgExt().equals("")) { - obvImageLocation = imageObvLocationInput.getText(); - addImage(obvImageLocation, true); + if(!bill.getObvImgPath().equals("")) { + setObvImageLocation(imageObvLocationInput.getText()); + addImage(getObvImageLocation(), true); } - if(!bill.getRevImgExt().equals("")) { - revImageLocation = imageRevLocationInput.getText(); - addImage(revImageLocation, true); + if(!bill.getRevImgPath().equals("")) { + setRevImageLocation(imageRevLocationInput.getText()); + addImage(getRevImageLocation(), true); } } - boolean saveBill() { + private void saveBill(int button) { String errorMessage = ""; bill.setName(typeInput.getText()); - if(countryInput.getText().trim().equals("")) - errorMessage += "Country must not be blank. If it's unknown or indistinguishable, enter \"Unknown\"."; - bill.setCountry(countryInput.getText()); + // Set country name + if(countryInput.getSelectedIndex() != -1) { + bill.setCountryName((countryInput).ids[countryInput.getSelectedIndex()]); + } + else { + if(!errorMessage.equals("")) + errorMessage += "\n"; + errorMessage += Main.getString("error_emptyCountry"); + } + + // Set currency abbreviation + if(currencyInput.getSelectedIndex() != -1) { + bill.getCurrency().setNameAbbr((currencyInput).ids[currencyInput.getSelectedIndex()]); + } + else { + if(!errorMessage.equals("")) + errorMessage += "\n"; + errorMessage += Main.getString("error_emptyCurrency"); + } try { bill.setYear(Integer.parseInt(yearInput.getText())); @@ -184,7 +231,7 @@ boolean saveBill() { catch (NumberFormatException er) { if(!errorMessage.equals("")) errorMessage += "\n"; - errorMessage += "Year format is incorrect. Cannot be blank and must be an integer (whole number)."; + errorMessage += Main.getString("error_emptyYear"); } String letter = seriesLetter.getText(); @@ -193,32 +240,40 @@ boolean saveBill() { else { if(!errorMessage.equals("")) errorMessage += "\n"; - errorMessage += "Series letter has max length of 3."; + errorMessage += Main.getString("error_seriesLetterLength"); } bill.setSerial(serialNumberInput.getText()); bill.setSignatures(signaturesInput.getText()); + if(locationDropDown.getSelectedItem() != null && + !locationDropDown.getSelectedItem().equals("")) { + bill.setContainerId(api.findContainer(locationDropDown.getSelectedItem().toString()).getId()); + } + try { bill.setDenomination(Double.parseDouble(denominationInput.getText())); } catch (NumberFormatException ex) { if(!errorMessage.equals("")) errorMessage += "\n"; - errorMessage += "Denomination format incorrect. Cannot be blank and must be a number."; + errorMessage += Main.getString("error_emptyDenomination"); } if(!valueInput.getText().equals("")) { try { bill.setValue(Double.parseDouble(valueInput.getText())); } catch (NumberFormatException e) { - errorMessage += "Value format incorrect. Must be a number or left blank."; + if(!errorMessage.equals("")) + errorMessage += "\n"; + errorMessage += Main.getString("error_emptyValue"); } } - bill.setPlateSeriesObv(plateSeriesObvInput.getText()); - bill.setPlateSeriesRev(plateSeriesRevInput.getText()); - bill.setDistrict(districtInput.getText()); - bill.setNotePosition(notePositionInput.getText()); + if(!imageObvLocationInput.getText().equals("" )&& !(new File(imageObvLocationInput.getText())).exists()) + errorMessage += MessageFormat.format(Main.getString("error_fileNotFound"), imageObvLocationInput.getText()) + "\n"; + if(!imageRevLocationInput.getText().equals("") && !(new File(imageRevLocationInput.getText())).exists()) + errorMessage += MessageFormat.format(Main.getString("error_fileNotFound"), imageRevLocationInput.getText()) + "\n"; + bill.setGraded(gradedCheckBox.isSelected()); if(gradeComboBox.getSelectedItem() != null) @@ -229,103 +284,107 @@ boolean saveBill() { if(errorCheckBox.isSelected()) bill.setErrorType(errorTypeInput.getText()); - bill.setStar(starCheck.isSelected()); + bill.setReplacement(starCheck.isSelected()); bill.setNote(Main.escapeForJava(noteInput.getText())); if(errorMessage.equals("")) { - int rows = bill.saveToDb(((Main) parent).databaseConnection); - String message = ((Main) parent).databaseConnection.wasSuccessful(rows); - - if(message.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - // Copy the image to a new location, then use that location - if(validObvImg) { - String path = imageObvLocationInput.getText(); - bill.setObvImgExt(path.substring(path.lastIndexOf('.'))); - if(Main.copyFile(imageObvLocationInput.getText(), - bill.getImagePath(true))) { - - String sql = "UPDATE Bills SET ObvImgExt=\"" + bill.getObvImgExt() + "\"\n" + - "WHERE ID=" + bill.getId() + ";"; - - rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - errorMessage += extMessage; + errorDisplay.setForeground(Color.BLACK); + errorDisplay.setText(Main.getString("addBill_message_saving")); + SwingWorker worker = new SwingWorker<>() { + + int rows = 0; + String returnMessage = ""; + String errorMessage = ""; + + @Override + protected Void doInBackground() { + rows = bill.saveToDb(api); + returnMessage = api.getSuccessMessage(rows); + + if(returnMessage.equals(NumismatistAPI.Companion.getString("db_message_success"))) { + try { + if(!bill.saveObvImage()) + { + errorMessage += MessageFormat.format(Main.getString("error_savingFile_message"), bill.getObvImgPath()); + } + } catch (SecurityException | IOException securityException ) { + errorMessage += securityException.getMessage(); + } + try { + if(!bill.saveRevImage()) + { + errorMessage += MessageFormat.format(Main.getString("error_savingFile_message"), bill.getRevImgPath()); + } + } catch (SecurityException | IOException securityException ) { + errorMessage += securityException.getMessage(); } } - else { - errorMessage = "Problem saving obverse image. Please try again."; - } - } - else { - // Delete the file - new File(bill.getImagePath(true)).delete(); - bill.setObvImgExt(""); - // Remove image extension from database - String sql = "UPDATE Bills SET ObvImgExt=null\n" + - "WHERE ID=" + bill.getId() + ";"; - - ((Main)parent).databaseConnection.runUpdate(sql); + return null; } - if(validRevImg) { - String path = imageRevLocationInput.getText(); - bill.setRevImgExt(path.substring(path.lastIndexOf('.'))); - - if(Main.copyFile(imageRevLocationInput.getText(), - bill.getImagePath(false))) { - - String sql = "UPDATE Bills SET RevImgExt=\"" + bill.getRevImgExt() + "\"\n" + - "WHERE ID=" + bill.getId() + ";"; - - rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - if (!errorMessage.equals("")) - errorMessage += "\n"; - errorMessage += extMessage; + + @Override + protected void done() { + if(returnMessage.equals(NumismatistAPI.Companion.getString("db_message_success"))) { + if(!errorMessage.equals("")) { + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(errorMessage); + } + else { + errorDisplay.setForeground(Main.COLOR_SUCCESS); + errorDisplay.setText( + MessageFormat.format(Main.getString("addBill_message_saved"), + bill.toString())); + + // Add to set if necessary + if(getParentSet() != null && !editingSet) + getParentSet().addItem(bill); + + if(button == getBUTTON_OK()) { + goHome(); + } + else if (button == getBUTTON_SAVE_NEW()) { + + setBill(new Bill()); + + if(getParentSet() != null) + getParent().setTitle(Main.getString("addBill_title_addToSet")); + else + getParent().setTitle(Main.getString("addBill_title_add")); + } + else if (button == getBUTTON_SAVE_COPY()) { + Bill newBill = bill.copy(); + newBill.setSet(null); + newBill.setId(DatabaseItem.ID_INVALID); + newBill.setObvImgPath(""); + newBill.setRevImgPath(""); + newBill.setContainerId(DatabaseItem.ID_INVALID); + + if(getParentSet() != null) { + newBill.setSet(getParentSet()); + getParent().setTitle(Main.getString("addBill_title_addToSet")); + } + else + getParent().setTitle(Main.getString("addBill_title_add")); + + setBill(newBill); + } } } else { - if (!errorMessage.equals("")) - errorMessage += "\n"; - errorMessage += "Problem saving reverse image. Please try again."; + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(returnMessage); } } - else { - // Delete the file - new File(bill.getImagePath(false)).delete(); - bill.setRevImgExt(""); - - // Remove image extension from database - String sql = "UPDATE Bills SET RevImgExt=null\n" + - "WHERE ID=" + bill.getId() + ";"; - - ((Main)parent).databaseConnection.runUpdate(sql); - } - - if(!errorMessage.equals("")) { - - errorDisplay.setForeground(Color.RED); - errorDisplay.setText(errorMessage); - - return false; - } - } - else { - errorDisplay.setForeground(Color.RED); - errorDisplay.setText(message); - return false; - } + }; - return true; + worker.execute(); } else { - errorDisplay.setForeground(Color.RED); + errorDisplay.setForeground(Main.COLOR_ERROR); errorDisplay.setText(errorMessage); - return false; } } @@ -339,89 +398,11 @@ public JPanel getPanel() { return addBillPanel; } - private void goHome() { - - if(fromCollection) { - CollectionTableScreen collectionTableScreen = new CollectionTableScreen(parent); - collectionTableScreen.setTab(2); - ((Main) parent).changeScreen(collectionTableScreen.getPanel(), "Collection"); - } - else - ((Main) parent).changeScreen(((Main) parent).getPanel(), ""); - } - - void addImage(String pathToImage, boolean obverse) { - - try { - ImageIO.read(new File(pathToImage)); - - if(obverse) { - obvImageLocation = pathToImage; - validObvImg = true; - // Force it to draw immediately - obvPicPanel.update(obvPicPanel.getGraphics()); - } - else { - revImageLocation = pathToImage; - validRevImg = true; - // Force it to draw immediately - revPicPanel.update(revPicPanel.getGraphics()); - } - - // Clear error text - errorDisplay.setText(""); - } - catch (IOException e) { - errorDisplay.setText("Error opening file"); - } - } - - void removeImage(boolean obverse) { - if(obverse) { - imageObvLocationInput.setText(""); - validObvImg = false; - } - else { - imageRevLocationInput.setText(""); - validRevImg = false; - } - - errorDisplay.setText(""); - } - - void openFileChooser(boolean obverse) { - final JFileChooser fc = new JFileChooser(); - - ImageFilter imageFilter = new ImageFilter(); - - fc.addChoosableFileFilter(imageFilter); - fc.setFileFilter(imageFilter); - int returnVal = fc.showOpenDialog(parent); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - - if(obverse) { - obvImageLocation = file.getAbsolutePath(); - imageObvLocationInput.setText(obvImageLocation); - } - else { - revImageLocation = file.getAbsolutePath(); - imageRevLocationInput.setText(revImageLocation); - } - - addImage(file.getAbsolutePath(), obverse); - } else if(returnVal != JFileChooser.CANCEL_OPTION) { - errorDisplay.setText("Error retrieving file"); - } - } - - public void setFromCollection(boolean fromCollection) { - this.fromCollection = fromCollection; - } - private void createUIComponents() { + scrollPane = new JScrollPane(); + scrollPane.setBorder(null); + // Allow resizing of images obvPicPanel = new JPanel() { @@ -430,7 +411,7 @@ public void paintComponent(Graphics g) { super.paintComponent(g); try { - Image img = ImageIO.read(new File(obvImageLocation)); + Image img = ImageIO.read(new File(getObvImageLocation())); double heightFactor = (float)getHeight() / img.getHeight(this); double widthFactor = (float)getWidth() / img.getWidth(this); @@ -464,7 +445,7 @@ public void paintComponent(Graphics g) { try { - Image img = ImageIO.read(new File(revImageLocation)); + Image img = ImageIO.read(new File(getRevImageLocation())); double heightFactor = (float)getHeight() / img.getHeight(this); double widthFactor = (float)getWidth() / img.getWidth(this); @@ -490,4 +471,8 @@ public void paintComponent(Graphics g) { } }; } + + public void setSet(Set set) { + this.setParentSet(set); + } } diff --git a/src/AddCoinScreen.form b/src/AddCoinScreen.form index 069cb18..a8f7456 100644 --- a/src/AddCoinScreen.form +++ b/src/AddCoinScreen.form @@ -1,184 +1,252 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - - - - - - - - + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - + + - + - + @@ -188,7 +256,7 @@ - + @@ -196,203 +264,274 @@ - + - + - + - + + + - + - + + + + + + + + + + + - + + + - + - + - + + + - - + + + + + - + + + - + - + - - + + - + + + + - + - - - + - + + - + - - + + - - - - + - + - + - + - - - - - - - - - + - + - + + - + - - - + - + + + + - + - - - + - + - + - - - + - - - + - + - + - + - + - + - + + - - - - - - - - - + - + - - - + - + - - - + - + - + - - + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + - - + + - - - + + + + + + + + + + diff --git a/src/AddCoinScreen.java b/src/AddCoinScreen.java index 1a79294..e4e73e1 100644 --- a/src/AddCoinScreen.java +++ b/src/AddCoinScreen.java @@ -1,15 +1,18 @@ -import items.Coin; -import items.CoinSet; -import items.DatabaseConnection; +import items.*; +import org.jetbrains.annotations.NotNull; import javax.imageio.ImageIO; import javax.swing.*; +import javax.swing.text.PlainDocument; import java.awt.*; import java.io.File; import java.io.IOException; import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; -public class AddCoinScreen { +public class AddCoinScreen extends AddSetItemScreen { private JButton cancelButton; private JPanel addCoinPanel; @@ -20,10 +23,13 @@ public class AddCoinScreen { private JButton nickelButton; private JButton dimeButton; private JButton quarterButton; + private JButton halfButton; + private JButton dollarButton; + private AutoComboBox currencyInput; private JTextField yearInput; - private JTextField countryInput; - private JComboBox mintMarkComboBox; - private JComboBox gradeComboBox; + private AutoComboBox countryInput; + private AutoComboBox mintMarkComboBox; + private AutoComboBox gradeComboBox; private JCheckBox gradedCheckBox; private JCheckBox errorCheckBox; private JTextField errorTypeInput; @@ -38,56 +44,79 @@ public class AddCoinScreen { private JButton revSetButton; private JButton revRemoveButton; private JTextArea noteInput; - private JButton addAnotherButton; + private JButton saveNewButton; private JPanel obvPicPanel; private JPanel revPicPanel; - - private final JFrame parent; - - private boolean editingSet = false; - private boolean fromCollection = false; + private JButton saveCopyButton; + private JComboBox locationDropDown; + private JButton addContainerButton; + private JScrollPane scrollPane; private Coin coin; - private CoinSet set = null; - - private boolean validObvImg = false; - private boolean validRevImg = false; - - private String obvImageLocation = ""; - private String revImageLocation = ""; public AddCoinScreen(JFrame parent) { this(parent, new Coin()); } - public AddCoinScreen(JFrame parent, final Coin coin) { - this.parent = parent; + public AddCoinScreen(JFrame parent, final Coin startCoin) { + super(parent); - this.coin = coin; + setReturnTab(TAB_COINS); + + setImageObvLocationInput(imageObvLocationInput); + setImageRevLocationInput(imageRevLocationInput); + + setObvPicPanel(obvPicPanel); + setRevPicPanel(revPicPanel); + + api = ((Main) parent).api; + + api.setCountryListener(new NumismatistAPI.CountryListener() { + @Override + public void countryListChanged(@NotNull ArrayList countries) { + //Update Country and Currency lists without changing selection + Object curCountry = countryInput.getSelectedItem(); + Object curCurrency = currencyInput.getSelectedItem(); + ComboBoxHelper.setupBoxes(countryInput, yearInput, currencyInput, errorDisplay, api); + if(curCountry != null && !curCountry.equals("")) + countryInput.setSelectedItem(curCountry); + if(curCurrency != null && !curCurrency.equals("")) + currencyInput.setSelectedItem(curCurrency); + } + }); + + this.coin = startCoin; + + // Used to format numbers + DecimalFormat format = new DecimalFormat(); + format.applyPattern("0.00"); - pennyButton.addActionListener(e -> denominationInput.setText("" + Coin.PENNY)); - nickelButton.addActionListener(e -> denominationInput.setText("" + Coin.NICKEL)); - dimeButton.addActionListener(e -> denominationInput.setText("" + Coin.DIME)); - quarterButton.addActionListener(e -> denominationInput.setText("" + Coin.QUARTER)); + pennyButton.addActionListener(e -> denominationInput.setText(format.format(Coin.PENNY))); + nickelButton.addActionListener(e -> denominationInput.setText(format.format(Coin.NICKEL))); + dimeButton.addActionListener(e -> denominationInput.setText(format.format(Coin.DIME))); + quarterButton.addActionListener(e -> denominationInput.setText(format.format(Coin.QUARTER))); + halfButton.addActionListener(e -> denominationInput.setText(format.format(Coin.HALF_DOLLAR))); + dollarButton.addActionListener(e -> denominationInput.setText(format.format(Coin.DOLLAR))); errorCheckBox.addActionListener(e -> errorTypeInput.setEnabled(errorCheckBox.isSelected())); - for(int i = 0; i < Coin.Companion.getMINT_MARKS().length; i++) { - mintMarkComboBox.addItem(Coin.Companion.getMINT_MARKS()[i]); - } + ComboBoxHelper.setupBoxes(countryInput, yearInput, currencyInput, errorDisplay, api); - for(int i = 0; i < Coin.Companion.getCONDITIONS().length; i++) { - gradeComboBox.addItem(Coin.Companion.getCONDITIONS()[i]); - } + // Restrict input + ((PlainDocument) denominationInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getDenominationFilter()); + ((PlainDocument) yearInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getCurrentYearFilter()); + ((PlainDocument) valueInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getValueFilter()); + + ComboBoxHelper.setContainerList(locationDropDown, api, addContainerButton, parent); obvSetButton.addActionListener(e -> { - obvImageLocation = imageObvLocationInput.getText(); - addImage(obvImageLocation, true); + setObvImageLocation(imageObvLocationInput.getText()); + addImage(getObvImageLocation(), true); }); revSetButton.addActionListener(e -> { - revImageLocation = imageRevLocationInput.getText(); - addImage(revImageLocation, false); + setRevImageLocation(imageRevLocationInput.getText()); + addImage(getRevImageLocation(), false); }); obvBrowseButton.addActionListener( e -> openFileChooser(true)); @@ -96,39 +125,44 @@ public AddCoinScreen(JFrame parent, final Coin coin) { obvRemoveButton.addActionListener(e -> removeImage(true)); revRemoveButton.addActionListener(e -> removeImage(false)); - addAnotherButton.addActionListener( e -> { - if(saveCoin()) { - errorDisplay.setForeground(Color.GREEN); - errorDisplay.setText("Coin saved!"); - - setCoin(new Coin()); - - editingSet = false; - if(set != null) - parent.setTitle("Add Coin to Set"); - else - parent.setTitle("Add Coin"); - } - }); - - OKButton.addActionListener(e -> { - - if(saveCoin()) - goHome(); - }); + saveNewButton.addActionListener(e -> saveCoin(getBUTTON_SAVE_NEW())); + saveCopyButton.addActionListener(e -> saveCoin(getBUTTON_SAVE_COPY())); + OKButton.addActionListener(e -> saveCoin(getBUTTON_OK())); cancelButton.addActionListener(e -> goHome()); + parent.getRootPane().setDefaultButton(OKButton); + // Set initial focus + SwingUtilities.invokeLater(() -> countryInput.requestFocus()); + setInfo(); } private void setInfo() { coinTypeInput.setText(coin.getName()); - countryInput.setText(coin.getCountry()); + String countryName = Country.Companion.getCountry(api.getCountries(), coin.getCountryName()).getName(); + + if(!countryName.isBlank()) + ((JTextField)countryInput.getEditor().getEditorComponent()).setText(countryName); + if(coin.getYear() != 0) yearInput.setText("" + coin.getYear()); else yearInput.setText(""); - mintMarkComboBox.setSelectedItem(coin.getMintMark()); + + ((JTextField)mintMarkComboBox.getEditor().getEditorComponent()).setText(coin.getMintMark()); + + // Must happen after country and year are set + try { + ComboBoxHelper.setCurrency(countryInput, yearInput, currencyInput, api.getCountries()); + } + catch (NumberFormatException nfe) { + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(Main.getString("error_invalidDate")); + } + + if(!coin.getCurrency().getNameAbbr().equals("")) { + ((JTextField) currencyInput.getEditor().getEditorComponent()).setText(coin.getCurrency().getName()); + } if(coin.getDenomination() != 0.0) { // Format the number properly @@ -153,18 +187,23 @@ private void setInfo() { valueInput.setText(""); gradedCheckBox.setSelected(coin.getGraded()); - gradeComboBox.setSelectedItem(coin.getCondition()); + ((JTextField)gradeComboBox.getEditor().getEditorComponent()).setText(coin.getCondition()); errorCheckBox.setSelected(coin.getError()); noteInput.setText(coin.getNote()); + locationDropDown.setSelectedItem(api.findContainer(coin.getContainerId()).getName()); + //((JTextField)locationDropDown.getEditor().getEditorComponent()).setText(api.findContainer(coin.getContainerId()).getName()); + // Set images - if(!coin.getObvImgExt().equals("")) { - obvImageLocation = coin.getImagePath(true); - addImage(obvImageLocation, true); + if(!coin.getObvImgPath().equals("")) { + setObvImageLocation(coin.getObvImgPath()); + addImage(getObvImageLocation(), true); + imageObvLocationInput.setText(getObvImageLocation()); } - if(!coin.getRevImgExt().equals("")) { - revImageLocation = coin.getImagePath(false); - addImage(revImageLocation, false); + if(!coin.getRevImgPath().equals("")) { + setRevImageLocation(coin.getRevImgPath()); + addImage(getRevImageLocation(), false); + imageRevLocationInput.setText(getRevImageLocation()); } } @@ -172,24 +211,49 @@ public JPanel getPanel() { return addCoinPanel; } - private boolean saveCoin() { + private void saveCoin(final int button) { coin.setName(coinTypeInput.getText()); - coin.setCountry(countryInput.getText()); // True if input is invalid boolean invalid = false; String invalidText = ""; + // Set country ID + if(countryInput.getSelectedIndex() != -1) { + coin.setCountryName(countryInput.ids[countryInput.getSelectedIndex()]); + } + else { + invalid = true; + if(!invalidText.equals("")) + invalidText += "\n"; + invalidText += Main.getString("error_emptyCountry"); + } + + // Set currency + if(currencyInput.getSelectedIndex() != -1) { + coin.setCurrency(api.findCurrency(currencyInput.ids[currencyInput.getSelectedIndex()])); + } + else { + invalid = true; + if(!invalidText.equals("")) + invalidText += "\n"; + invalidText += Main.getString("error_emptyCurrency"); + } + try { coin.setYear(Integer.parseInt(yearInput.getText())); } catch (NumberFormatException ex) { invalid = true; - invalidText += "Year format is incorrect. Cannot be blank and must be an integer (whole number)."; + if(!invalidText.equals("")) + invalidText += "\n"; + invalidText += Main.getString("error_emptyYear"); } - if(mintMarkComboBox.getSelectedItem() != null) - coin.setMintMark(mintMarkComboBox.getSelectedItem().toString()); + if(((JTextField)mintMarkComboBox.getEditor().getEditorComponent()).getText().equals("")) + coin.setMintMark(""); + else + coin.setMintMark(((JTextField)mintMarkComboBox.getEditor().getEditorComponent()).getText()); try { coin.setDenomination(Double.parseDouble(denominationInput.getText())); @@ -197,8 +261,7 @@ private boolean saveCoin() { invalid = true; if (!invalidText.equals("")) invalidText += "\n"; - - invalidText += "Denomination format incorrect. Cannot be blank and must be a number."; + invalidText += Main.getString("error_emptyDenomination"); } // If value box is not empty @@ -210,12 +273,12 @@ private boolean saveCoin() { if (!invalidText.equals("")) invalidText += "\n"; - invalidText += "Value format incorrect. Must be a number."; + invalidText += Main.getString("error_emptyValue"); } } - if(gradeComboBox.getSelectedItem() != null) - coin.setCondition(gradeComboBox.getSelectedItem().toString()); + if(((JTextField)gradeComboBox.getEditor().getEditorComponent()).getText() != null) + coin.setCondition(((JTextField)gradeComboBox.getEditor().getEditorComponent()).getText()); coin.setGraded(gradedCheckBox.isSelected()); coin.setError(errorCheckBox.isSelected()); @@ -227,216 +290,105 @@ private boolean saveCoin() { coin.setNote(Main.escapeForJava(noteInput.getText())); + if(locationDropDown.getSelectedItem() != null && + !locationDropDown.getSelectedItem().equals("")) { + coin.setContainerId(api.findContainer(locationDropDown.getSelectedItem().toString()).getId()); + } + + if(getValidObvImg()) + coin.setObvImgPath(imageObvLocationInput.getText()); + if(getValidRevImg()) + coin.setRevImgPath(imageRevLocationInput.getText()); + // If some fields contain invalid data if(invalid) { - errorDisplay.setForeground(Color.RED); + errorDisplay.setForeground(Main.COLOR_ERROR); errorDisplay.setText(invalidText); } else { - int rows = coin.saveToDb(((Main)parent).databaseConnection); - - String successMessage = ((Main)parent).databaseConnection.wasSuccessful(rows); - - if(successMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)){ - errorDisplay.setText(""); - - // Add to set if necessary - if(set != null && !editingSet) - set.addCoin(coin); - - String errorMessage = ""; + errorDisplay.setForeground(Color.BLACK); + errorDisplay.setText(Main.getString("addCoin_message_saving")); + SwingWorker worker = new SwingWorker<>() { - // Copy the image to a new location, then use that location - if(validObvImg) { - String path = imageObvLocationInput.getText(); - coin.setObvImgExt(path.substring(path.lastIndexOf('.'))); + int rows = 0; - if(Main.copyFile(imageObvLocationInput.getText(), - coin.getImagePath(true))) { - - String sql = "UPDATE Coins SET ObvImgExt=\"" + coin.getObvImgExt() + "\"\n" + - "WHERE ID=" + coin.getId() + ";"; - - rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - errorMessage += extMessage; - } - } - else { - errorMessage = "Problem saving obverse image. Please try again."; - } - } - else { - // Delete the file - new File(coin.getImagePath(true)).delete(); - coin.setObvImgExt(""); - - // Remove image extension from database - String sql = "UPDATE Coins SET ObvImgExt=null\n" + - "WHERE ID=" + coin.getId() + ";"; - - ((Main)parent).databaseConnection.runUpdate(sql); + @Override + public Void doInBackground() { + rows = coin.saveToDb(api); + return null; } - if(validRevImg) { - String path = imageRevLocationInput.getText(); - coin.setRevImgExt(path.substring(path.lastIndexOf('.'))); - - if(Main.copyFile(imageRevLocationInput.getText(), - coin.getImagePath(false))) { - String sql = "UPDATE Coins SET RevImgExt=\"" + coin.getRevImgExt() + "\"\n" + - "WHERE ID=" + coin.getId() + ";"; + @Override + public void done() { + String successMessage = api.getSuccessMessage(rows); + + if(successMessage.equals(NumismatistAPI.Companion.getString("db_message_success"))){ + errorDisplay.setForeground(Main.COLOR_SUCCESS); + errorDisplay.setText(MessageFormat.format(Main.getString("addCoin_message_saved"), + coin.toString())); + + // Add to set if necessary + if(getParentSet() != null && !getEditingParent()) + getParentSet().addItem(coin); + + if(button == getBUTTON_SAVE_COPY()) { + Coin newCoin = coin.copy(); + newCoin.setSet(null); + newCoin.setId(DatabaseItem.ID_INVALID); + newCoin.setSlotId(DatabaseItem.ID_INVALID); + newCoin.setObvImgPath(""); + newCoin.setRevImgPath(""); + newCoin.setContainerId(DatabaseItem.ID_INVALID); + + if(getParentSet() != null) { + newCoin.setSet(getParentSet()); + getParent().setTitle(Main.getString("addCoin_title_addToSet")); + } + else + getParent().setTitle(Main.getString("addCoin_title_add")); + + setCoin(newCoin); + } + else if(button == getBUTTON_SAVE_NEW()) { + setCoin(new Coin()); - rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - if (!errorMessage.equals("")) - errorMessage += extMessage; + if(getParentSet() != null) + getParent().setTitle(Main.getString("addCoin_title_addToSet")); + else + getParent().setTitle(Main.getString("addCoin_title_add")); } + else if(button == getBUTTON_OK()) + goHome(); } else { - if (!errorMessage.equals("")) - errorMessage += "\n"; - errorMessage += "Problem saving reverse image. Please try again."; + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(successMessage); } } - else { - // Delete the file - new File(coin.getImagePath(false)).delete(); - coin.setRevImgExt(""); - - // Remove image extension from database - String sql = "UPDATE Coins SET RevImgExt=null\n" + - "WHERE ID=" + coin.getId() + ";"; - - ((Main)parent).databaseConnection.runUpdate(sql); - } - - if(!errorMessage.equals("")) { - invalid = true; - errorDisplay.setForeground(Color.RED); - errorDisplay.setText(errorMessage); - } - } - else { - errorDisplay.setText(successMessage); - invalid = true; - } - } - - return !invalid; - } - - private void goHome() { - if(set != null) { - AddSetScreen setScreen = new AddSetScreen(parent, set); - setScreen.setFromCollection(fromCollection); - setScreen.setInfo(); + }; - ((Main) parent).changeScreen(setScreen.getPanel(), ""); - } - else if(fromCollection) { - CollectionTableScreen collectionTableScreen = new CollectionTableScreen(parent); - - ((Main) parent).changeScreen(collectionTableScreen.getPanel(), "Collection"); - } - else { - ((Main) parent).changeScreen(((Main) parent).getPanel(), ""); + worker.execute(); } } - void addImage(String pathToImage, boolean obverse) { - - try { - ImageIO.read(new File(pathToImage)); - - if(obverse) { - obvImageLocation = pathToImage; - validObvImg = true; - // Force it to draw immediately - obvPicPanel.update(obvPicPanel.getGraphics()); - } - else { - revImageLocation = pathToImage; - validRevImg = true; - // Force it to draw immediately - revPicPanel.update(revPicPanel.getGraphics()); - } - - // Clear error text - errorDisplay.setText(""); - } - catch (IOException e) { - errorDisplay.setText("Error opening file"); - } - } - - void removeImage(boolean obverse) { - if(obverse) { - imageObvLocationInput.setText(""); - validObvImg = false; - } - else { - imageRevLocationInput.setText(""); - validRevImg = false; - } - - errorDisplay.setText(""); - } - - void openFileChooser(boolean obverse) { - final JFileChooser fc = new JFileChooser(); - - ImageFilter imageFilter = new ImageFilter(); - - fc.addChoosableFileFilter(imageFilter); - fc.setFileFilter(imageFilter); - int returnVal = fc.showOpenDialog(parent); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - - if(obverse) { - obvImageLocation = file.getAbsolutePath(); - imageObvLocationInput.setText(obvImageLocation); - } - else {revImageLocation = file.getAbsolutePath(); - imageRevLocationInput.setText(revImageLocation); - } - - addImage(file.getAbsolutePath(), obverse); - } else if(returnVal != JFileChooser.CANCEL_OPTION) { - errorDisplay.setText("Error retrieving file"); - } - } - - void setEditingSet(boolean editing) { - this.editingSet = editing; - } - - public void setSet(CoinSet set) { - this.set = set; - - setInfo(); - } - - public CoinSet getSet() { - return set; - } - public void setCoin(Coin coin) { this.coin = coin; setInfo(); } - public void setFromCollection(boolean fromCollection) { - this.fromCollection = fromCollection; - } - private void createUIComponents() { + countryInput = new AutoComboBox(); + currencyInput = new AutoComboBox(); + gradeComboBox = new AutoComboBox(); + mintMarkComboBox = new AutoComboBox(); + scrollPane = new JScrollPane(); + scrollPane.setBorder(null); + + ComboBoxHelper.setKeyWord(mintMarkComboBox, new ArrayList(Arrays.asList(Coin.Companion.getMINT_MARKS()))); + ComboBoxHelper.setKeyWord(gradeComboBox, new ArrayList(Arrays.asList(Coin.Companion.getCONDITIONS()))); + // Allow resizing of images obvPicPanel = new JPanel() { @@ -445,26 +397,28 @@ public void paintComponent(Graphics g) { super.paintComponent(g); try { - Image img = ImageIO.read(new File(obvImageLocation)); + Image img = ImageIO.read(new File(getObvImageLocation())); - double heightFactor = (float)getHeight() / img.getHeight(this); - double widthFactor = (float)getWidth() / img.getWidth(this); + if(img != null) { - double scaleFactor = Math.min(heightFactor, widthFactor); + double heightFactor = (float) getHeight() / img.getHeight(this); + double widthFactor = (float) getWidth() / img.getWidth(this); - int newHeight = (int)(img.getHeight(this) * scaleFactor); - int newWidth = (int)(img.getWidth(this) * scaleFactor); + double scaleFactor = Math.min(heightFactor, widthFactor); - Image scaled; - // Only scale image if it's larger than we want - if(scaleFactor < 1) { - // Scale to new size - scaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); - } - else - scaled = img; + int newHeight = (int) (img.getHeight(this) * scaleFactor); + int newWidth = (int) (img.getWidth(this) * scaleFactor); - g.drawImage(scaled, 0, 0, null); + Image scaled; + // Only scale image if it's larger than we want + if (scaleFactor < 1) { + // Scale to new size + scaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + } else + scaled = img; + + g.drawImage(scaled, 0, 0, null); + } } catch (IOException ignore) { } } @@ -479,7 +433,7 @@ public void paintComponent(Graphics g) { try { - Image img = ImageIO.read(new File(revImageLocation)); + Image img = ImageIO.read(new File(getRevImageLocation())); double heightFactor = (float)getHeight() / img.getHeight(this); double widthFactor = (float)getWidth() / img.getWidth(this); diff --git a/src/AddSetItemScreen.kt b/src/AddSetItemScreen.kt new file mode 100644 index 0000000..88ce5f7 --- /dev/null +++ b/src/AddSetItemScreen.kt @@ -0,0 +1,172 @@ +import items.DatabaseItem +import items.Set +import java.io.File +import java.io.IOException +import java.text.MessageFormat +import javax.imageio.ImageIO +import javax.swing.JFileChooser +import javax.swing.JFrame +import javax.swing.JPanel +import javax.swing.JTextField + +open class AddSetItemScreen(var parent: JFrame) { + + companion object { + // Determine which tab should be shown upon returning to collection screen + const val TAB_COINS = 0 + const val TAB_BILLS = 2 + const val TAB_SETS = 1 + } + + var returnTab = TAB_COINS + + var parentSet: Set? = null + var editingParent = false + var previousScreen: AddSetScreen? = null + + var imageObvLocationInput: JTextField? = null + var imageRevLocationInput: JTextField? = null + + var obvPicPanel: JPanel? = null + var revPicPanel: JPanel? = null + + var obvImageLocation = "" + var revImageLocation = "" + + var validObvImg = false + var validRevImg = false + + var fromCollection = false + + val BUTTON_OK = 1 + val BUTTON_CANCEL= 2; + val BUTTON_SAVE_NEW = 3 + val BUTTON_SAVE_COPY = 4 + + lateinit var api: NumismatistAPI + + open fun addImage(pathToImage: String, obverse: Boolean) : Boolean { + return try { + ImageIO.read(File(pathToImage)) + if (obverse) { + obvImageLocation = pathToImage + validObvImg = true + // Force it to draw immediately + obvPicPanel?.update(obvPicPanel?.graphics) + } else { + revImageLocation = pathToImage + validRevImg = true + // Force it to draw immediately + revPicPanel?.update(revPicPanel?.graphics) + } + true + } catch (e: IOException) { + false + } + } + + /** + * Opens a file chooser to choose an image for this item. + * + * @param obverse True if this image is for the obverse (front) of the item, false if for the reverse (back) + * + * @return Path of the chosen file + */ + open fun openFileChooser(obverse: Boolean) : String { + val fc = JFileChooser() + val imageFilter = ImageFilter() + + val imageLocation : String + + fc.addChoosableFileFilter(imageFilter) + fc.fileFilter = imageFilter + fc.currentDirectory = File(Main.getSettingLastDirectory()) + val returnVal = fc.showOpenDialog(parent) + if (returnVal == JFileChooser.APPROVE_OPTION) { + val file = fc.selectedFile + if (obverse) { + obvImageLocation = file.absolutePath + imageObvLocationInput?.text = obvImageLocation + Main.setSettingLastDirectory(obvImageLocation) + } else { + revImageLocation = file.absolutePath + imageRevLocationInput?.text = revImageLocation + Main.setSettingLastDirectory(revImageLocation) + } + imageLocation = file.absolutePath + addImage(file.absolutePath, obverse) + } else if (returnVal != JFileChooser.CANCEL_OPTION) { + imageLocation = "" + } + else + imageLocation = "" + + return imageLocation + } + + /** + * Removes an image from this item. + * + * @param obverse True if this image is for the obverse (front) of the item, false if for the reverse (back) + */ + open fun removeImage(obverse: Boolean) { + if (obverse) { + obvImageLocation = "" + imageObvLocationInput!!.text = "" + obvPicPanel!!.update(obvPicPanel!!.graphics) + validObvImg = true + } else { + revImageLocation = "" + imageRevLocationInput!!.text = "" + revPicPanel!!.update(revPicPanel!!.graphics) + validRevImg = true + } + } + + fun goHome() { + // If this item is in a set + if (parentSet != null) { + val setScreen: AddSetScreen + // Theoretically, this should never be false + if (previousScreen != null) + setScreen = previousScreen!! + else { + setScreen = AddSetScreen(parent, parentSet) + setScreen.fromCollection = fromCollection + } + + val newTitle = + if (previousScreen != null && setScreen.set.id == DatabaseItem.ID_INVALID) { + if (previousScreen!!.parentSet != null) { + if(previousScreen!!.parentSet!!.name != "") + MessageFormat.format(Main.getString("addSet_title_addToSet"), previousScreen!!.parentSet!!.name) + else + MessageFormat.format(Main.getString("addSet_title_addToSet"), Main.getString("property_set_toString")) + } else { + Main.getString("addSet_title_add") + } + } else if (parentSet?.id == DatabaseItem.ID_INVALID) { + Main.getString("addSet_title_add") + } + else { + val name = if(parentSet!!.name == "") + Main.getString("property_set_toString") + else + parentSet!!.name + + MessageFormat.format(Main.getString("addSet_title_edit"), name) + } + + (parent as Main).changeScreen(setScreen.panel, newTitle) + } + // If previous screen was collection screen + else if (fromCollection) { + val collectionTableScreen = CollectionTableScreen(parent) + collectionTableScreen.setTab(returnTab) + (parent as Main).changeScreen(collectionTableScreen.panel, Main.getString("viewColl_title")) + } + else { + (parent as Main).changeScreen((parent as Main).panel, "") + } + } +} \ No newline at end of file diff --git a/src/AddSetScreen.form b/src/AddSetScreen.form index 5da14d0..cc42d37 100644 --- a/src/AddSetScreen.form +++ b/src/AddSetScreen.form @@ -1,82 +1,33 @@
- + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - + - - - - - - - - - - - - - - - - - - - + @@ -85,230 +36,383 @@ - + + + - + - + + - + - + - + - + + - + - - - + - + + - + + + + + + + + + + + + + + - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + - + - + + - + - + + + + + + + + + - + + - + - - + + - - + + + + + + + + + + + + - + + + - + + - + + - + - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - + - + - + + - + - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/AddSetScreen.java b/src/AddSetScreen.java index 57fdc51..789ef78 100644 --- a/src/AddSetScreen.java +++ b/src/AddSetScreen.java @@ -1,17 +1,22 @@ -import items.Coin; -import items.CoinSet; -import items.DatabaseConnection; +import items.*; +import org.jetbrains.annotations.NotNull; import javax.imageio.ImageIO; import javax.swing.*; +import javax.swing.text.PlainDocument; import java.awt.*; +import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.ArrayList; -public class AddSetScreen { +import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE; + +public class AddSetScreen extends AddSetItemScreen { private JTextField nameInput; private JButton cancelButton; @@ -19,14 +24,14 @@ public class AddSetScreen { private JTextField yearInput; private JTextField valueInput; private JLabel faceValueDisplay; - private JButton addNewCoinButton; - private JButton addExistingCoinButton; + private JButton addNewButton; + private JButton addExistingButton; private JList coinList; - private DefaultListModel listModel; + private final DefaultListModel listModel; private JPanel panel; private JTextArea noteInput; private JTextArea errorDisplay; - private JButton addAnotherButton; + private JButton saveNewButton; private JTextField imageObvLocationInput; private JButton obvSetButton; private JButton obvBrowseButton; @@ -37,65 +42,79 @@ public class AddSetScreen { private JButton revBrowseButton; private JPanel obvPicPanel; private JPanel revPicPanel; - private JButton anotherSameButton; private JButton setCoinsButton; + private JButton saveCopyButton; + private JComboBox locationDropDown; + private JButton addContainerButton; + private JScrollPane scrollPane; - private CoinSet set; + private Set originalSet = new Set(); + private Set set = new Set(); - private final JFrame parent; + public AddSetScreen(JFrame parent) { + this(parent, new Set()); + } - private String obvImageLocation = ""; - private String revImageLocation = ""; + public AddSetScreen(JFrame parent, Set set) { + super(parent); - private boolean validObvImg = false; - private boolean validRevImg = false; + setReturnTab(TAB_SETS); - private boolean fromCollection = false; + if(set == null) + set = new Set(); - public AddSetScreen(JFrame parent, CoinSet set) { - this.parent = parent; + originalSet = set.copy(); - listModel = new DefaultListModel(); + api = ((Main) parent).api; - cancelButton.addActionListener(e -> goHome()); + setImageObvLocationInput(imageObvLocationInput); + setImageRevLocationInput(imageRevLocationInput); - OKButton.addActionListener(e -> { + setObvPicPanel(obvPicPanel); + setRevPicPanel(revPicPanel); - if(saveSet()) { - this.set.getRemovedCoins().clear(); - goHome(); - } - }); + listModel = new DefaultListModel(); - addAnotherButton.addActionListener(e -> { - if(saveSet()) { - setSet(new CoinSet()); - errorDisplay.setForeground(Color.GREEN); - errorDisplay.setText("Set Saved!"); + // Restrict input + ((PlainDocument) yearInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getCurrentYearFilter()); + ((PlainDocument) valueInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getValueFilter()); - parent.setTitle("Add Set"); - } - }); + ComboBoxHelper.setContainerList(locationDropDown, api, addContainerButton, parent); - anotherSameButton.addActionListener( e -> { - if(saveSet()) { - CoinSet newSet = this.set.copy(); - setSet(newSet); - errorDisplay.setForeground(Color.GREEN); - errorDisplay.setText("Set Saved!"); + cancelButton.addActionListener(e -> { + // TODO: I shouldn't have to go through this + // Find items that were added to the set and remove them + ArrayList removeItems = new ArrayList<>(); + for(SetItem item : this.set.getItems()) { + if(!originalSet.getItems().contains(item)) + removeItems.add(item); + } + for(SetItem item : removeItems) + this.set.removeItem(item); - parent.setTitle("Add Set"); + // Find items that were originally in the set but are now missing and re-add them + for(SetItem item : originalSet.getItems() ) { + if(!this.set.getItems().contains(item)) { + this.set.addItem(item); + } } + + // Set all other properties back to their original values + setSet(originalSet); + goHome(); }); + OKButton.addActionListener(e -> saveSet(getBUTTON_OK())); + saveNewButton.addActionListener(e -> saveSet(getBUTTON_SAVE_NEW())); + saveCopyButton.addActionListener( e -> saveSet(getBUTTON_SAVE_COPY())); obvSetButton.addActionListener(e -> { - obvImageLocation = imageObvLocationInput.getText(); - addImage(obvImageLocation, true); + setObvImageLocation(imageObvLocationInput.getText()); + addImage(getObvImageLocation(), true); }); revSetButton.addActionListener(e -> { - revImageLocation = imageRevLocationInput.getText(); - addImage(revImageLocation, true); + setRevImageLocation(imageRevLocationInput.getText()); + addImage(getRevImageLocation(), true); }); @@ -105,27 +124,72 @@ public AddSetScreen(JFrame parent, CoinSet set) { obvRemoveButton.addActionListener(e -> removeImage(true)); revRemoveButton.addActionListener(e -> removeImage(false)); - addNewCoinButton.addActionListener( e -> { - setSetFromInput(); - showCoinScreen(new Coin()); + addNewButton.addActionListener(e -> { + + //Create the popup menu + final JPopupMenu popup = new JPopupMenu(); + popup.add(new JMenuItem(new AbstractAction(Main.getString("addSet_menu_newCoin")) { + public void actionPerformed(ActionEvent e) { + setSetFromInput(); + showCoinScreen(new Coin()); + } + })); + popup.add(new JMenuItem(new AbstractAction(Main.getString("addSet_menu_newBill")) { + public void actionPerformed(ActionEvent e) { + setSetFromInput(); + showBillScreen(new Bill()); + } + })); + popup.add(new JMenuItem(new AbstractAction(Main.getString("addSet_menu_newSet")) { + public void actionPerformed(ActionEvent e) { + setSetFromInput(); + showSetScreen(new Set()); + } + })); + + popup.show(addNewButton, 0, addNewButton.getHeight()); }); - addExistingCoinButton.addActionListener(e -> { + addExistingButton.addActionListener(e -> { setSetFromInput(); - ExistingCoinDialog existingCoinDialog = new ExistingCoinDialog(parent, this.set); - Coin newCoin = existingCoinDialog.showDialog(); - if(newCoin != null && !this.set.getCoins().contains(newCoin)) - addCoin(newCoin); + JDialog existingItemDialog = new JDialog(parent); + CollectionTableScreen collectionTableScreen = new CollectionTableScreen(parent, existingItemDialog); + collectionTableScreen.setSet(this.set); + + String name; + + if(this.set.getName().equals("")) + name = Main.getString("property_set_toString").toLowerCase(); + else + name = this.set.getName(); + + existingItemDialog.add(collectionTableScreen.getPanel()); + existingItemDialog.pack(); + existingItemDialog.setTitle(MessageFormat.format(Main.getString("addSet_popUp_title"), name)); + existingItemDialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + existingItemDialog.setSize(800,600); + existingItemDialog.setMinimumSize(new Dimension(400,300)); + existingItemDialog.setLocationRelativeTo(parent); + existingItemDialog.setModal(true); + existingItemDialog.setVisible(true); + + // Runs when dialog closed + if(collectionTableScreen.selectedItem != null) + getSet().getItems().add(collectionTableScreen.selectedItem); + //getSet().addItem(collectionTableScreen.selectedItem); + setInfo(); + + // Reset default button, as it seems to get lost after closing the dialog + parent.getRootPane().setDefaultButton(OKButton); }); coinList.setLayoutOrientation(JList.VERTICAL); - if(set == null) - set = new CoinSet(); - setSet(set); + setInfo(); + // Add right click listener coinList.addMouseListener(new MouseAdapter() { @Override @@ -142,25 +206,30 @@ public void mousePressed(MouseEvent e) { int maxX = selectedCell.x + selectedCell.width; int maxY = selectedCell.y + selectedCell.height; - // If clicked inside of a cell + // If clicked in a cell if ((e.getX() > selectedCell.getX()) && (e.getX() < maxX) && (e.getY() > selectedCell.getY()) && (e.getY() < maxY) ) { coinList.setSelectedIndex(index); - Coin selectedCoin = getCoin(index); + SetItem selectedItem = getItem(index); // Create right click menu final JPopupMenu coinListRightClickMenu = new JPopupMenu(); - JMenuItem editCoin = new JMenuItem("Edit"); + JMenuItem editCoin = new JMenuItem(Main.getString("addSet_rtClickItem_edit")); editCoin.addActionListener(e1 -> { setSetFromInput(); - // Show edit coin screen - showCoinScreen(selectedCoin); + // Show edit screen + if(selectedItem instanceof Coin) + showCoinScreen((Coin)selectedItem); + else if(selectedItem instanceof Bill) + showBillScreen((Bill) selectedItem); + else + showSetScreen((Set) selectedItem); }); - JMenuItem removeCoin = new JMenuItem("Remove from Set"); + JMenuItem removeCoin = new JMenuItem(Main.getString("addSet_rtClickItem_remove")); removeCoin.addActionListener(e1 ->{ - removeCoin(selectedCoin); + removeItem(selectedItem); setInfo(); }); coinListRightClickMenu.add(editCoin); @@ -174,39 +243,39 @@ public void mousePressed(MouseEvent e) { } }); setCoinsButton.addActionListener(e -> setCoinsToYear()); + + parent.getRootPane().setDefaultButton(OKButton); + // Set initial focus + SwingUtilities.invokeLater(() -> nameInput.requestFocus()); } private void setCoinsToYear() { - for (Coin coin : set.getCoins()) { + for (SetItem item : set.getItems()) { try { set.setYear(Integer.parseInt(yearInput.getText())); - coin.setYear(set.getYear()); + item.setYear(set.getYear()); setCoinList(); } catch (NumberFormatException ex) { - errorDisplay.setForeground(Color.RED); - errorDisplay.setText("Year format is incorrect. Must be an integer (whole number)"); + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(Main.getString("Year cannot be blank.")); } } } - private Coin getCoin(int index) { - return set.getCoins().get(index); + private SetItem getItem(int index) { + return set.getItems().get(index); } - private boolean removeCoin(Coin coin) { - return set.removeCoin(coin); + private boolean removeItem(SetItem item) { + return set.removeItem(item); } - public AddSetScreen(JFrame parent) { - this(parent, new CoinSet()); - } - - void setInfo() { + private void setInfo() { nameInput.setText(set.getName()); - if(set.getYear() !=0) + if(set.getYear() != Set.YEAR_NONE) yearInput.setText("" + set.getYear()); else yearInput.setText(""); @@ -216,14 +285,16 @@ void setInfo() { valueInput.setText(""); noteInput.setText(set.getNote()); + locationDropDown.setSelectedItem(api.findContainer(set.getContainerId()).getName()); + // Set images - if(!set.getObvImgExt().equals("")) { - obvImageLocation = imageObvLocationInput.getText(); - addImage(obvImageLocation, true); + if(!set.getObvImgPath().equals("")) { + setObvImageLocation(imageObvLocationInput.getText()); + addImage(getObvImageLocation(), true); } - if(!set.getRevImgExt().equals("")) { - revImageLocation = imageRevLocationInput.getText(); - addImage(revImageLocation, true); + if(!set.getRevImgPath().equals("")) { + setRevImageLocation(imageRevLocationInput.getText()); + addImage(getRevImageLocation(), true); } setCoinList(); @@ -250,13 +321,13 @@ void setSetFromInput() { set.setNote(Main.escapeForJava(noteInput.getText())); } - boolean saveSet() { + private void saveSet(int button) { String errorMessage = ""; if(!nameInput.getText().equals("")) set.setName(nameInput.getText()); else - errorMessage += "Name cannot be blank."; + errorMessage += Main.getString("error_emptyName"); if(!yearInput.getText().equals("")) try { @@ -265,7 +336,7 @@ boolean saveSet() { catch (NumberFormatException ex) { if(!errorMessage.equals("")) errorMessage += "\n"; - errorMessage += "Year format is incorrect. Must be an integer (whole number) or blank."; + errorMessage += Main.getString("error_emptyYear"); } if(!valueInput.getText().equals("")) { @@ -276,7 +347,7 @@ boolean saveSet() { if(!errorMessage.equals("")) errorMessage += "\n"; - errorMessage += "Value format incorrect. Must be a number or blank."; + errorMessage += Main.getString("error_emptyValue"); } } else @@ -284,195 +355,161 @@ boolean saveSet() { set.setNote(Main.escapeForJava(noteInput.getText())); + if(locationDropDown.getSelectedItem() != null && + !locationDropDown.getSelectedItem().equals("")) { + set.setContainerId(api.findContainer(locationDropDown.getSelectedItem().toString()).getId()); + } + if(errorMessage.equals("")) { - String message = set.saveToDb(((Main)parent).databaseConnection); - - // Check for success - if(message.equals(DatabaseConnection.SUCCESS_MESSAGE) || - message.equals(DatabaseConnection.NO_CHANGE_MESSAGE) || - message.equals("")) { - // Copy the image to a new location, then use that location - if(validObvImg) { - String path = imageObvLocationInput.getText(); - set.setObvImgExt(path.substring(path.lastIndexOf('.'))); - - if(Main.copyFile(imageObvLocationInput.getText(), - set.getImagePath(true))) { - - String sql = "UPDATE Sets SET ObvImgExt=\"" + set.getObvImgExt() + "\"\n" + - "WHERE ID=" + set.getId() + ";"; - - int rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - errorMessage += extMessage; - } - } - else { - errorMessage = "Problem saving obverse image. Please try again."; - } - } - else { - try { - // Delete the file - new File(set.getImagePath(true)).delete(); - set.setObvImgExt(""); - // Remove image extension from database - String sql = "UPDATE Sets SET ObvImgExt=null\n" + - "WHERE ID=" + set.getId() + ";"; + errorDisplay.setForeground(Color.BLACK); + errorDisplay.setText(Main.getString("addSet_message_saving")); + SwingWorker worker = new SwingWorker<>() { + String errorMessage = ""; + String returnMessage = ""; - ((Main) parent).databaseConnection.runUpdate(sql); + @Override + protected Void doInBackground() { + int rows; + try { + rows = set.saveToDb(api); + returnMessage = api.getSuccessMessage(rows); } - catch (Exception ignore) {} + catch (IOException ioe) { + returnMessage = ioe.getMessage(); + } + + return null; } - if(validRevImg) { - String path = imageRevLocationInput.getText(); - set.setRevImgExt(path.substring(path.lastIndexOf('.'))); - if(Main.copyFile(imageRevLocationInput.getText(), - set.getImagePath(false))) { + @Override + protected void done() { + // Check for success + if(returnMessage.equals(NumismatistAPI.Companion.getString("db_message_success")) || + returnMessage.equals(NumismatistAPI.Companion.getString("db_message_noChange")) || + returnMessage.equals("")) { + + if(getValidObvImg()) { + try { + if(!set.saveObvImage()) + { + errorMessage += MessageFormat.format(Main.getString("error_savingFile_message"), set.getObvImgPath()); + } + } catch (SecurityException | IOException securityException ) { + errorMessage += securityException.getMessage(); + } + } + else { + try { + if(!set.deleteObvImage()) + { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), set.getObvImgPath()); + } + } catch (SecurityException | IOException securityException ) { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += securityException.getMessage(); + } + } - String sql = "UPDATE Sets SET RevImgExt=\"" + set.getRevImgExt() + "\"\n" + - "WHERE ID=" + set.getId() + ";"; + if(getValidRevImg()) { + try { + if(!set.saveRevImage()) + { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += MessageFormat.format(Main.getString("error_savingFile_message"), set.getRevImgPath()); + } + } catch (SecurityException | IOException securityException ) { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += securityException.getMessage(); + } + } + else { + try { + if(!set.deleteRevImage()) + { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), set.getRevImgPath()); + } + } catch (SecurityException | IOException securityException ) { + if(!errorMessage.equals("")) + errorMessage += "\n"; + + errorMessage += securityException.getMessage(); + } + } - int rows = ((Main) parent).databaseConnection.runUpdate(sql); - String extMessage = ((Main) parent).databaseConnection.wasSuccessful(rows); - if (!extMessage.equals(DatabaseConnection.SUCCESS_MESSAGE)) { - if (!errorMessage.equals("")) - errorMessage += extMessage; + if(!errorMessage.equals("")) { + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(errorMessage); + } + else { + errorDisplay.setForeground(Main.COLOR_SUCCESS); + errorDisplay.setText(MessageFormat.format(Main.getString("addSet_message_saved"), set.toString())); + + // Add to set if necessary + if(getParentSet() != null && !getEditingParent()) + getParentSet().addItem(set); + + if(button == getBUTTON_OK()) { + set.getRemovedItems().clear(); + goHome(); + } + else if(button == getBUTTON_SAVE_NEW()) { + setSet(new Set()); + + getParent().setTitle(Main.getString("addSet_title_add")); + } + else if(button == getBUTTON_SAVE_COPY()) { + + Set newSet = set.copy(); + newSet.setSet(null); + newSet.setId(DatabaseItem.ID_INVALID); + newSet.setObvImgPath(""); + newSet.setRevImgPath(""); + newSet.setContainerId(DatabaseItem.ID_INVALID); + + if(getParentSet() != null) { + newSet.setSet(getParentSet()); + getParent().setTitle(Main.getString("addSet_title_addToSet")); + } + else + getParent().setTitle(Main.getString("addSet_title_add")); + + setSet(newSet); + } } } else { - if (!errorMessage.equals("")) - errorMessage += "\n"; - errorMessage += "Problem saving reverse image. Please try again."; - } - } - else { - try { - // Delete the file - new File(set.getImagePath(false)).delete(); - set.setRevImgExt(""); - - // Remove image extension from database - String sql = "UPDATE Sets SET RevImgExt=null\n" + - "WHERE ID=" + set.getId() + ";"; - - ((Main) parent).databaseConnection.runUpdate(sql); + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(returnMessage); } - catch (Exception ignore) {} } - if(!errorMessage.equals("")) { + }; - errorDisplay.setForeground(Color.RED); - errorDisplay.setText(errorMessage); - - return false; - } - } - else { - errorDisplay.setForeground(Color.RED); - errorDisplay.setText(message); - return false; - } - return true; + worker.execute(); } else { - errorDisplay.setForeground(Color.RED); + errorDisplay.setForeground(Main.COLOR_ERROR); errorDisplay.setText(errorMessage); } - - return false; } JPanel getPanel() { return panel; } - private void goHome() { - if(fromCollection) { - CollectionTableScreen collectionTableScreen = new CollectionTableScreen(parent); - collectionTableScreen.setTab(1); - ((Main) parent).changeScreen(collectionTableScreen.getPanel(), "Collection"); - } - else - ((Main) parent).changeScreen(((Main) parent).getPanel(), ""); - } - - void addCoin(Coin newCoin) { - set.addCoin(newCoin); - - setInfo(); - } - - void addImage(String pathToImage, boolean obverse) { - - try { - ImageIO.read(new File(pathToImage)); - - if(obverse) { - obvImageLocation = pathToImage; - validObvImg = true; - // Force it to draw immediately - obvPicPanel.update(obvPicPanel.getGraphics()); - } - else { - revImageLocation = pathToImage; - validRevImg = true; - // Force it to draw immediately - revPicPanel.update(revPicPanel.getGraphics()); - } - - // Clear error text - errorDisplay.setText(""); - } - catch (IOException e) { - errorDisplay.setText("Error opening file"); - } - } - - void removeImage(boolean obverse) { - if(obverse) { - imageObvLocationInput.setText(""); - validObvImg = false; - } - else { - imageRevLocationInput.setText(""); - validRevImg = false; - } - - errorDisplay.setText(""); - } - - void openFileChooser(boolean obverse) { - final JFileChooser fc = new JFileChooser(); - - ImageFilter imageFilter = new ImageFilter(); - - fc.addChoosableFileFilter(imageFilter); - fc.setFileFilter(imageFilter); - int returnVal = fc.showOpenDialog(parent); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - - if(obverse) { - obvImageLocation = file.getAbsolutePath(); - imageObvLocationInput.setText(obvImageLocation); - } - else { - revImageLocation = file.getAbsolutePath(); - imageRevLocationInput.setText(revImageLocation); - } - - addImage(file.getAbsolutePath(), obverse); - } else if(returnVal != JFileChooser.CANCEL_OPTION) { - errorDisplay.setText("Error retrieving file"); - } - } - void setFaceValueDisplay() { // Format the number properly // Necessary due to inaccuracies caused by binary decimal calculations @@ -480,10 +517,10 @@ void setFaceValueDisplay() { format.applyPattern("0.00"); String value = format.format(set.getFaceValue()); - faceValueDisplay.setText("$" + value); + faceValueDisplay.setText("" + value); } - public void setSet(CoinSet set) { + public void setSet(Set set) { this.set = set; setInfo(); @@ -492,38 +529,94 @@ public void setSet(CoinSet set) { void setCoinList() { listModel.removeAllElements(); - for(int i = 0; i< set.getCoins().size(); i++ ) - listModel.addElement(set.getCoins().get(i)); + for(int i = 0; i< set.getItems().size(); i++ ) + listModel.addElement(set.getItems().get(i)); coinList.setModel(listModel); coinList.invalidate(); } private void showCoinScreen(Coin coin) { - AddCoinScreen addCoinScreen = new AddCoinScreen(parent); - addCoinScreen.setSet(this.set); - addCoinScreen.setFromCollection(fromCollection); - addCoinScreen.setCoin(coin); + AddCoinScreen addCoinScreen = new AddCoinScreen(getParent(), coin); + addCoinScreen.setParentSet(this.set); + addCoinScreen.setFromCollection(getFromCollection()); + addCoinScreen.setPreviousScreen(this); + + if(set.getItems().contains(coin)) + addCoinScreen.setEditingParent(true); + + String name; - if(set.getCoins().contains(coin)) - addCoinScreen.setEditingSet(true); + if(set.getName().equals("")) + name = Main.getString("property_set_toString"); + else + name = set.getName(); if(coin.getId() != 0) - ((Main)parent).changeScreen(addCoinScreen.getPanel(), "Edit New Coin in Set"); + ((Main) getParent()).changeScreen(addCoinScreen.getPanel(), MessageFormat.format(Main.getString("addCoin_title_editInSet"), name)); else - ((Main)parent).changeScreen(addCoinScreen.getPanel(), "Add New Coin to Set"); + ((Main) getParent()).changeScreen(addCoinScreen.getPanel(), MessageFormat.format(Main.getString("addCoin_title_addToSet"), name)); } - public CoinSet getSet() { - return set; + private void showBillScreen(Bill bill) { + AddBillScreen addBillScreen = new AddBillScreen(getParent(), bill); + addBillScreen.setSet(this.set); + addBillScreen.setFromCollection(getFromCollection()); + addBillScreen.setPreviousScreen(this); + + if(set.getItems().contains(bill)) + addBillScreen.setEditingParent(true); + + String name; + + if(set.getName().equals("")) + name = Main.getString("property_set_toString"); + else + name = set.getName(); + + if(bill.getId() != 0) + ((Main) getParent()).changeScreen(addBillScreen.getPanel(), MessageFormat.format(Main.getString("addBill_title_editInSet"),name)); + else + ((Main) getParent()).changeScreen(addBillScreen.getPanel(), MessageFormat.format(Main.getString("addBill_title_addToSet"),name)); } - public void setFromCollection(boolean fromCollection) { - this.fromCollection = fromCollection; + private void showSetScreen(Set newSet) { + AddSetScreen addSetScreen = new AddSetScreen(getParent(), newSet); + addSetScreen.setParentSet(this.set); + addSetScreen.setFromCollection(getFromCollection()); + addSetScreen.setPreviousScreen(this); + + if(set.getItems().contains(newSet)) + addSetScreen.setEditingParent(true); + + String newName; + + if(newSet.getName().equals("")) + newName = Main.getString("property_set_toString"); + else + newName = newSet.getName(); + + if(newSet.getId() != 0) { + ((Main) getParent()).changeScreen(addSetScreen.getPanel(), MessageFormat.format(Main.getString("addSet_title_edit"), newName)); + } + else { + String name = set.getName(); + if(name.equals("")) + name = Main.getString("property_set_toString"); + String newTitle = MessageFormat.format(Main.getString("addSet_title_addToSet"), name); + ((Main) getParent()).changeScreen(addSetScreen.getPanel(), newTitle); + } + } + + public Set getSet() { + return set; } private void createUIComponents() { + scrollPane = new JScrollPane(); + scrollPane.setBorder(null); + // Allow resizing of images obvPicPanel = new JPanel() { @@ -532,7 +625,7 @@ public void paintComponent(Graphics g) { super.paintComponent(g); try { - Image img = ImageIO.read(new File(obvImageLocation)); + Image img = ImageIO.read(new File(getObvImageLocation())); double heightFactor = (float)getHeight() / img.getHeight(this); double widthFactor = (float)getWidth() / img.getWidth(this); @@ -566,7 +659,7 @@ public void paintComponent(Graphics g) { try { - Image img = ImageIO.read(new File(revImageLocation)); + Image img = ImageIO.read(new File(getRevImageLocation())); double heightFactor = (float)getHeight() / img.getHeight(this); double widthFactor = (float)getWidth() / img.getWidth(this); diff --git a/src/AutoComboBox.java b/src/AutoComboBox.java new file mode 100644 index 0000000..cb9dce8 --- /dev/null +++ b/src/AutoComboBox.java @@ -0,0 +1,97 @@ +import java.util.*; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JTextField; + +public class AutoComboBox extends JComboBox { + + String[] keyWord = {}; + String[] ids = {}; + Vector myVector = new Vector(); + + @SuppressWarnings({"unchecked"}) + public AutoComboBox() { + + setModel(new DefaultComboBoxModel(myVector)); + setSelectedIndex(-1); + setEditable(true); + JTextField text = (JTextField) this.getEditor().getEditorComponent(); + text.setFocusable(true); + text.setText(""); + text.addKeyListener(new ComboListener(this, myVector)); + setMyVector(); + } + + /** + * set the item list of the AutoComboBox + * @param keyWord an array of Strings + */ + public void setKeyWord(String[] keyWord) { + this.keyWord = keyWord; + setMyVectorInitial(); + + // redisplay popup if it's visible + if(isPopupVisible()) { + hidePopup(); + showPopup(); + } + } + + @SuppressWarnings("unchecked") + private void setMyVector() { + Collections.addAll(myVector, keyWord); + } + + @SuppressWarnings("unchecked") + private void setMyVectorInitial() { + myVector.clear(); + Collections.addAll(myVector, keyWord); + } + + /** + * Determine if an array contains the same list of items as the keyWord + * variable + * + * @param arr2 Array to compare to + * @return True if the array contains the same items, otherwise false + */ + public boolean keyWordEquals(String[] arr2) { + + // If they're different sizes, they aren't the same + if(keyWord.length != arr2.length) + return false; + + // Store arr1[] elements and their counts in hash map + Map map = new HashMap<>(); + int count; + for (String s : keyWord) { + if (map.get(s) == null) + map.put(s, 1); + else { + count = map.get(s); + count++; + map.put(s, count); + } + } + + // Traverse arr2[] elements and check if all elements of arr2[] are + // present same number of times or not. + for (String s : arr2) { + // If there is an element in arr2[], but not in arr1[] + if (!map.containsKey(s)) + return false; + + // If an element of arr2[] appears more times than it appears in + // keyword[] + if (map.get(s) == 0) + return false; + + count = map.get(s); + --count; + map.put(s, count); + } + + // If it got here they have the same elements, the same number of times + return true; + } +} \ No newline at end of file diff --git a/src/BillInfoDialog.form b/src/BillInfoDialog.form deleted file mode 100644 index 5cfaf1a..0000000 --- a/src/BillInfoDialog.form +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/BillInfoDialog.java b/src/BillInfoDialog.java deleted file mode 100644 index ed73700..0000000 --- a/src/BillInfoDialog.java +++ /dev/null @@ -1,128 +0,0 @@ -import javax.imageio.ImageIO; -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -public class BillInfoDialog extends JDialog { - private JPanel contentPane; - private JButton buttonOK; - private JTextPane topTextArea; - private JTextPane plateSeriesExplanation; - private JTextPane districtExplanation; - private JTextPane notePositionDescription; - private JScrollPane scrollPane; - private JLabel districtImageLabel; - private JLabel plateSeriesObvImageLabel; - private JLabel notePositionImageLabel; - private JLabel plateSeriesRevImageLabel; - - public BillInfoDialog(JFrame parent) { - super(parent); - setContentPane(contentPane); - setModal(true); - getRootPane().setDefaultButton(buttonOK); - setMinimumSize(new Dimension(400, 300)); - setTitle("Bill Information"); - setResizable(false); - - // close on ESCAPE - contentPane.registerKeyboardAction(e -> dispose(), - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - topTextArea.setText("Dollar bills have a lot of information on them. " + - "Some of this information is useful for determining the value of a bill."); - - districtExplanation.setText("The district is probably the most important of these items for " + - "determining the value of a bill. The district identifies where the note was printed. " + - "Districts are identified by both a letter and a number." + - "Only the letter OR the number is required to identify a district."); - - plateSeriesExplanation.setText("The plate series identifies engraving plate was used to strike " + - "this specific bill. There is a plate series printed on the front and the back of the " + - "bill. These number will be different, as different plates are used to press each side. " + - "The number is located in the bottom right of the bill on both sides."); - - notePositionDescription.setText("The note position can be found in the upper left of the bill. " + - "It identifies where on the plate this bill was struck. It is a letter followed by a " + - "number. Each plate is split into quadrants, identified by the number, and each " + - "quadrant has multiple locations for bills to be struck, identified by the letter. " + - "This letter-number combination can identify exactly where on the plate the bill " + - "was located."); - - String os = System.getProperty("os.name"); - String slash; - if (os.toLowerCase().contains("windows")) - slash = "\\"; - else - slash = "/"; - - // Add images - try { - BufferedImage image = ImageIO.read(new File( - "." + slash + "images" + slash + "District.jpg")); - - districtImageLabel.setIcon(new ImageIcon(scaleImage(image))); - } - catch (IOException ignore) { - } - - try { - BufferedImage image = ImageIO.read(new File( - "." + slash + "images" + slash + "Note_Position.jpg")); - - notePositionImageLabel.setIcon(new ImageIcon(scaleImage(image))); - } - catch (IOException ignore) { - } - - try { - BufferedImage image = ImageIO.read(new File( - "." + slash + "images" + slash + "Plate_Series_obv.jpg")); - - plateSeriesObvImageLabel.setIcon(new ImageIcon(scaleImage(image))); - } - catch (IOException ignore) { - } - - try { - BufferedImage image = ImageIO.read(new File( - "." + slash + "images" + slash + "Plate_Series_rev.jpg")); - - plateSeriesRevImageLabel.setIcon(new ImageIcon(scaleImage(image))); - } - catch (IOException ignore) { - } - - // Scroll to the top, since for some reason it scrolls down as things are added. - javax.swing.SwingUtilities.invokeLater(() -> scrollPane.getVerticalScrollBar().setValue(0)); - - buttonOK.addActionListener(e -> onOK()); - } - - private void onOK() { - // add your code here - dispose(); - } - - private Image scaleImage(BufferedImage img) { - double scaleFactor = (float)355 / img.getWidth(this); - - int newHeight = (int)(img.getHeight(this) * scaleFactor); - int newWidth = (int)(img.getWidth(this) * scaleFactor); - - Image scaled; - // Only scale image if it's larger than we want - if(scaleFactor < 1) { - // Scale to new size - scaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); - } - else - scaled = img; - - return scaled; - } -} diff --git a/src/BookPageDisplay.form b/src/BookPageDisplay.form new file mode 100644 index 0000000..34df45d --- /dev/null +++ b/src/BookPageDisplay.form @@ -0,0 +1,107 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BookPageDisplay.java b/src/BookPageDisplay.java new file mode 100644 index 0000000..6665e74 --- /dev/null +++ b/src/BookPageDisplay.java @@ -0,0 +1,160 @@ +import items.BookPage; +import items.Coin; +import items.PageSlot; + +import javax.swing.*; +import java.io.IOException; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; + +public class BookPageDisplay extends MyScreen { + + private BookPage page; + private JPanel panel; + private JLabel titleLabel; + private JLabel subtitleLabel; + private JPanel displayPanel; + private JButton buttonOK; + private JButton buttonCancel; + private JButton nextPageButton; + private JButton prevPageButton; + + BookPageHelper bookPageHelper = null; + + JFrame parent; + + private final NumismatistAPI api; + + public BookPageDisplay(JFrame parent) { + super(parent); + + api = ((Main) parent).api; + setPanel(panel); + this.parent = parent; + + displayPanel.setLayout(new BoxLayout(displayPanel, BoxLayout.Y_AXIS)); + + buttonOK.addActionListener(e -> { + + StringBuilder errorMessage = new StringBuilder(); + + // Delete coins in delete list + for(Coin coin : page.getCoinsToDelete()) { + try { + if(coin.removeFromDb(api)) { + if (!coin.deleteObvImage()) { + if(!errorMessage.toString().equals("")) + errorMessage.append("\n"); + + errorMessage.append(MessageFormat.format(Main.getString("error_deletingFile_message"), coin.getObvImgPath())); + } + if (!coin.deleteRevImage()) { + if(!errorMessage.toString().equals("")) + errorMessage.append("\n"); + + errorMessage.append(MessageFormat.format(Main.getString("error_deletingFile_message"), coin.getRevImgPath())); + } + } + } catch (SecurityException | IOException | SQLException securityException) { + if(!errorMessage.toString().equals("")) + errorMessage.append("\n"); + + errorMessage.append(securityException.getMessage()); + } + } + + if(errorMessage.toString().equals("")) { + + // Save book + int rows = getPage().getBook().saveToDb(api); + + if(rows == 1) + ((Main) parent).changeScreen(((Main) parent).getPanel(), ""); + else { + errorMessage.append(api.getSuccessMessage(rows)); + } + } + + // Display error message + if(!errorMessage.toString().equals("")) { + JOptionPane.showMessageDialog(parent, errorMessage.toString(), + Main.getString("error_genericTitle"), JOptionPane.ERROR_MESSAGE); + } + }); + + buttonCancel.addActionListener(e -> ((Main) parent).changeScreen(((Main) parent).getPanel(), "")); + + // Set margins around the edges of the page + getPanel().setBorder(BorderFactory.createEmptyBorder(15,15,15,15)); + + // Handle next page and previous page clicks + prevPageButton.addActionListener(e -> { + BookPageDisplay pageDisplay = new BookPageDisplay(parent, page.getBook().getPages().get(page.getPageNum()-2), bookPageHelper); + ((Main) parent).changeScreen(pageDisplay.getPanel(), MessageFormat.format(Main.getString("bookPage_title"),page.getPageNum()-1)); + }); + + nextPageButton.addActionListener(e -> { + BookPageDisplay pageDisplay = new BookPageDisplay(parent, page.getBook().getPages().get(page.getPageNum()), bookPageHelper); + ((Main) parent).changeScreen(pageDisplay.getPanel(), MessageFormat.format(Main.getString("bookPage_title"),(page.getPageNum()+1))); + }); + } + + public BookPageDisplay(JFrame parent, BookPage page) { + this(parent); + + setPage(page); + titleLabel.setText(page.getBook().getTitle()); + String subtitle = MessageFormat.format(Main.getString("bookPage_title"),page.getPageNum()); + subtitleLabel.setText(subtitle); + + if(page.getPageNum() == 1) { + prevPageButton.setEnabled(false); + } + if(page.getPageNum() == page.getBook().getPages().size()) { + nextPageButton.setEnabled(false); + } + } + + public BookPageDisplay(JFrame parent, BookPage page, BookPageHelper helper) { + this(parent, page); + + bookPageHelper = helper; + } + + private void setPage(BookPage page) { + this.page = page; + + displayPanel.removeAll(); + + for(ArrayList row : page.getRows()) { + addRow(row); + } + } + + public BookPage getPage() { + return page; + } + + public void addRow(ArrayList slots) { + JPanel row = new JPanel(); + row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS)); + + // Show each slot + for(PageSlot slot : slots) { + PageSlotPanel rowPanel = new PageSlotPanel(slot.getSize(), slot.getLine1Text(), slot.getLine2Text()); + rowPanel.page = this; + if(bookPageHelper == null) + bookPageHelper = new BookPageHelper(); + rowPanel.setSlot(slot); + slot.setBookPage(page); + + // Show which slots are full + rowPanel.getRadioButton().setSelected(slot.isSlotFilled()); + + row.add(rowPanel); + } + + displayPanel.add(row); + } +} \ No newline at end of file diff --git a/src/BookPageHelper.kt b/src/BookPageHelper.kt new file mode 100644 index 0000000..897feff --- /dev/null +++ b/src/BookPageHelper.kt @@ -0,0 +1,260 @@ +import items.PageSlot +import java.awt.image.BufferedImage +import javax.imageio.ImageIO +import javax.swing.ImageIcon +import javax.swing.JRadioButton + +class BookPageHelper { + + companion object { + const val MODE_EMPTY = 1 + const val MODE_EMPTY_HOVERED = 2 + const val MODE_FILLED = 3 + const val MODE_FILLED_HOVERED = 4 + } + + private val penny : ImageIcon + private val pennyEmpty : ImageIcon + private val pennyEmptyHovered : ImageIcon + private val pennyHovered : ImageIcon + private val nickel : ImageIcon + private val nickelEmpty : ImageIcon + private val nickelEmptyHovered : ImageIcon + private val nickelHovered : ImageIcon + private val dime : ImageIcon + private val dimeEmpty : ImageIcon + private val dimeEmptyHovered : ImageIcon + private val dimeHovered : ImageIcon + private val quarter : ImageIcon + private val quarterEmpty : ImageIcon + private val quarterEmptyHovered : ImageIcon + private val quarterHovered : ImageIcon + private val half : ImageIcon + private val halfEmpty : ImageIcon + private val halfEmptyHovered : ImageIcon + private val halfHovered : ImageIcon + private val dollar : ImageIcon + private val dollarEmpty : ImageIcon + private val dollarEmptyHovered : ImageIcon + private val dollarHovered : ImageIcon + + init { + + val resPath = NumismatistAPI.getResPath("images") + + // "cache" the images + penny = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}cent.png") + ) + ), PageSlot.SIZE_PENNY, PageSlot.SIZE_PENNY + ) + pennyEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_PENNY, PageSlot.SIZE_PENNY) + pennyEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_PENNY, PageSlot.SIZE_PENNY) + pennyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}cent_hover.png"))), + PageSlot.SIZE_PENNY, PageSlot.SIZE_PENNY) + + nickel = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}nickel.png")) + ), PageSlot.SIZE_NICKEL, PageSlot.SIZE_NICKEL) + nickelEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_NICKEL, PageSlot.SIZE_NICKEL) + nickelEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_NICKEL, PageSlot.SIZE_NICKEL) + nickelHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}nickel_hover.png") + ) + ), PageSlot.SIZE_NICKEL, PageSlot.SIZE_NICKEL + ) + + dime = resizeIcon (ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}dime.png") + ) + ), PageSlot.SIZE_DIME, PageSlot.SIZE_DIME) + dimeEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_DIME, PageSlot.SIZE_DIME) + dimeEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_DIME, PageSlot.SIZE_DIME) + dimeHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}dime_hover.png") + ) + ), PageSlot.SIZE_DIME, PageSlot.SIZE_DIME + ) + + quarter = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}quarter.png") + ) + ), PageSlot.SIZE_QUARTER, PageSlot.SIZE_QUARTER + ) + quarterEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_QUARTER, PageSlot.SIZE_QUARTER) + quarterEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_QUARTER, PageSlot.SIZE_QUARTER) + quarterHovered = resizeIcon (ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}quarter_hover.png") + ) + ), PageSlot.SIZE_QUARTER, PageSlot.SIZE_QUARTER) + + half = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}half.png") + ) + ), PageSlot.SIZE_HALF, PageSlot.SIZE_HALF + ) + halfEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_HALF, PageSlot.SIZE_HALF) + halfEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_HALF, PageSlot.SIZE_HALF) + halfHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}half_hover.png") + ) + ), PageSlot.SIZE_HALF, PageSlot.SIZE_HALF + ) + + dollar = resizeIcon (ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}dollar.png") + ) + ), PageSlot.SIZE_DOLLAR, PageSlot.SIZE_DOLLAR) + dollarEmpty = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton.png"))), + PageSlot.SIZE_DOLLAR, PageSlot.SIZE_DOLLAR) + dollarEmptyHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}radioButton_hover.png"))), + PageSlot.SIZE_DOLLAR, PageSlot.SIZE_DOLLAR) + dollarHovered = resizeIcon( + ImageIcon( + ImageIO.read( + NumismatistAPI.getFileFromRes("${resPath}dollar_hover.png") + ) + ), PageSlot.SIZE_DOLLAR, PageSlot.SIZE_DOLLAR + ) + } + + /** + * Resizes a BufferedImage. Takes transparency into effect + * + * @param originalImage The image to resize + * @param newWidth How wide to make the new image + * @param newHeight How tall to make the new image + * + * @return The newly resized image + */ + private fun resizeImage(originalImage: BufferedImage?, newWidth: Int, newHeight: Int): BufferedImage { + + val resizedImage = BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB) + val graphics2D = resizedImage.createGraphics() + graphics2D.drawImage(originalImage, 0, 0, newWidth, newHeight, null) + graphics2D.dispose() + return resizedImage + } + + /** + * Resizes an Icon. Takes transparency into effect + * + * @param originalIcon The icon to resize + * @param newWidth How wide to make the new icon + * @param newHeight How tall to make the new icon + * + * @return The newly resized icon + */ + private fun resizeIcon(originalIcon: ImageIcon, newWidth: Int, newHeight: Int) : ImageIcon { + // Turn icon into BufferedImage + val iconImage = BufferedImage(originalIcon.iconWidth, originalIcon.iconWidth, BufferedImage.TYPE_INT_ARGB) + val g = iconImage.createGraphics() + originalIcon.paintIcon(JRadioButton(), g, 0,0) + + // Resize, turn back into icon, and return + return ImageIcon(resizeImage(iconImage, newWidth, newHeight)) + } + + fun getIcon(mode: Int, size: Int) : ImageIcon { + when(mode) { + MODE_EMPTY_HOVERED -> return when(size) { + PageSlot.SIZE_PENNY -> pennyEmptyHovered + PageSlot.SIZE_NICKEL -> nickelEmptyHovered + PageSlot.SIZE_QUARTER -> quarterEmptyHovered + PageSlot.SIZE_HALF -> halfEmptyHovered + PageSlot.SIZE_DOLLAR -> dollarEmptyHovered + else -> dimeEmptyHovered + } + MODE_FILLED -> return when(size) { + PageSlot.SIZE_PENNY -> penny + PageSlot.SIZE_NICKEL -> nickel + PageSlot.SIZE_QUARTER -> quarter + PageSlot.SIZE_HALF -> half + PageSlot.SIZE_DOLLAR -> dollar + else -> dime + } + MODE_FILLED_HOVERED -> return when(size) { + PageSlot.SIZE_PENNY -> pennyHovered + PageSlot.SIZE_NICKEL -> nickelHovered + PageSlot.SIZE_QUARTER -> quarterHovered + PageSlot.SIZE_HALF -> halfHovered + PageSlot.SIZE_DOLLAR -> dollarHovered + else -> dimeHovered + } + else -> return when(size) { + PageSlot.SIZE_PENNY -> pennyEmpty + PageSlot.SIZE_NICKEL -> nickelEmpty + PageSlot.SIZE_QUARTER -> quarterEmpty + PageSlot.SIZE_HALF -> halfEmpty + PageSlot.SIZE_DOLLAR -> dollarEmpty + else -> dimeEmpty + } + } + } +} \ No newline at end of file diff --git a/src/CollectionTableScreen.form b/src/CollectionTableScreen.form index ac104cc..bc8967b 100644 --- a/src/CollectionTableScreen.form +++ b/src/CollectionTableScreen.form @@ -18,32 +18,34 @@ - + - + - - - - - - + - + - + - - - + + + + + + + + + +
@@ -54,17 +56,7 @@ - - - - - - - - - - - +
diff --git a/src/CollectionTableScreen.java b/src/CollectionTableScreen.java index 1860b11..3c37c17 100644 --- a/src/CollectionTableScreen.java +++ b/src/CollectionTableScreen.java @@ -1,72 +1,114 @@ -import items.Bill; -import items.Coin; -import items.CoinSet; +import items.*; +import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableModel; -import javax.swing.table.TableRowSorter; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import javax.swing.table.*; +import java.awt.event.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.sql.SQLException; import java.text.DecimalFormat; +import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; +// TODO: Add tree view - especially for sets and/or container view. +// Build custom tree view public class CollectionTableScreen { private JButton OKButton; private JTabbedPane tabbedPane; private JPanel panel; private JButton exportButton; + private JButton cancelButton; - private final JTable coinsTable; - private final JTable setsTable; - private final JTable billsTable; + private final MyTable coinsTable; + private final MyTable coinsTotalTable; + private final MyTable setsTable; + private final MyTable setsTotalTable; + private final MyTable billsTable; + private final MyTable billsTotalTable; - JFrame parent; + private final JFrame parent; private ArrayList coins; - private ArrayList sets; + private ArrayList sets; private ArrayList bills; + private Set set = null; + + public SetItem selectedItem = null; + + private ActionListener okListener; + + private final NumismatistAPI api; + public CollectionTableScreen(JFrame parent) { this.parent = parent; - JScrollPane scrollPane1 = new JScrollPane(); - JScrollPane scrollPane2 = new JScrollPane(); - JScrollPane scrollPane3 = new JScrollPane(); - coinsTable = new JTable(); - setsTable = new JTable(); - billsTable = new JTable(); + api = ((Main) parent).api; + + // Add Panels + JPanel coinsPanel = new JPanel(); + JPanel setsPanel = new JPanel(); + JPanel billsPanel = new JPanel(); + coinsPanel.setLayout(new BoxLayout(coinsPanel, BoxLayout.PAGE_AXIS)); + setsPanel.setLayout(new BoxLayout(setsPanel, BoxLayout.PAGE_AXIS)); + billsPanel.setLayout(new BoxLayout(billsPanel, BoxLayout.PAGE_AXIS)); + + // Add scroll panes + JScrollPane coinsScrollPane = new JScrollPane(); + JScrollPane setsScrollPane = new JScrollPane(); + JScrollPane billsScrollPane = new JScrollPane(); + + // Setup tables + coinsTable = new MyTable(); + coinsTotalTable = new MyTable(); + setsTable = new MyTable(); + setsTotalTable = new MyTable(); + billsTable = new MyTable(); + billsTotalTable = new MyTable(); coinsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + setsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + billsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); setCoinsTable(); - scrollPane1.getViewport().add(coinsTable); + coinsScrollPane.getViewport().add(coinsTable); setSetsTable(); - scrollPane2.getViewport().add(setsTable); + setsScrollPane.getViewport().add(setsTable); setBillsTable(); - scrollPane3.getViewport().add(billsTable); - - // Remove the default tab - tabbedPane.removeTabAt(0); - - tabbedPane.addTab("Coins", scrollPane1); - tabbedPane.addTab("Sets", scrollPane2); - tabbedPane.addTab("Bills", scrollPane3); - - OKButton.addActionListener( e-> goHome()); + billsScrollPane.getViewport().add(billsTable); + + coinsPanel.add(coinsScrollPane); + coinsPanel.add(coinsTotalTable); + tabbedPane.addTab(Main.getString("viewColl_tab_coins"), coinsPanel); + setsPanel.add(setsScrollPane); + setsPanel.add(setsTotalTable); + tabbedPane.addTab(Main.getString("viewColl_tab_sets"), setsPanel); + billsPanel.add(billsScrollPane); + billsPanel.add(billsTotalTable); + tabbedPane.addTab(Main.getString("viewColl_tab_bills"), billsPanel); + + okListener = e -> goHome(); + + OKButton.addActionListener(okListener); + + // Resize columns to fit content + coinsTable.resizeColumns(); + coinsTotalTable.resizeColumns(); + setsTotalTable.resizeColumns(); + setsTotalTable.resizeColumns(); + billsTable.resizeColumns(); + billsTotalTable.resizeColumns(); exportButton.addActionListener( e -> { JFileChooser fc = new JFileChooser(); - fc.setFileFilter(new ExcelFilter()); + fc.setFileFilter(new CsvFilter()); fc.setAcceptAllFileFilterUsed(false); int returnVal = fc.showSaveDialog(parent); @@ -94,12 +136,110 @@ public CollectionTableScreen(JFrame parent) { } if(!acceptable) - path += ".xlsx"; + path += ".csv"; export(path); } }); + + api.setCoinListener(new NumismatistAPI.CoinListener() { + @Override + public void coinListRetrievedFromFb(@NotNull ArrayList coins) { + setCoinsTable(); + } + }); + api.setBillListener(new NumismatistAPI.BillListener() { + @Override + public void billListRetrievedFromFb(@NotNull ArrayList bills) { + setBillsTable(); + } + }); + api.setSetListener(new NumismatistAPI.SetListener() { + @Override + public void setListRetrievedFromFb(@NotNull ArrayList sets) { + setSetsTable(); + } + }); + + parent.getRootPane().setDefaultButton(OKButton); + } + + public CollectionTableScreen(JFrame parent, JDialog dialog) { + this(parent); + + exportButton.setVisible(false); + cancelButton.setVisible(true); + + cancelButton.addActionListener(e -> { + selectedItem = null; + dialog.setVisible(false); + }); + + // Remove old listener + OKButton.removeActionListener(okListener); + + dialog.getRootPane().setDefaultButton(OKButton); + + //create and add new listener + okListener = e -> { + + Object idString; + + if(tabbedPane.getSelectedIndex() == 0) { + + if(coinsTable.getSelectedRow() == -1) { + JOptionPane.showMessageDialog(parent, Main.getString("viewColl_error_noSelectionMessage"), + Main.getString("viewColl_error_noSelectionTitle"), JOptionPane.ERROR_MESSAGE); + } + else { + idString = coinsTable.getValueAt(coinsTable.getSelectedRow(), 0); + // If didn't select empty row + if (idString != null) { + int id = Integer.parseInt(idString.toString()); + for (Coin coin : coins) + if (coin.getId() == id) + selectedItem = coin; + } + dialog.setVisible(false); + } + } + else if(tabbedPane.getSelectedIndex() == 1) { + if(setsTable.getSelectedRow() == -1) { + JOptionPane.showMessageDialog(parent, Main.getString("viewColl_error_noSelectionMessage"), + Main.getString("viewColl_error_noSelectionTitle"), JOptionPane.ERROR_MESSAGE); + } + else { + idString = setsTable.getValueAt(setsTable.getSelectedRow(), 0); + // If didn't select empty row + if (idString != null) { + int id = Integer.parseInt(idString.toString()); + for (Set selectedSet : sets) + if (selectedSet.getId() == id) + selectedItem = selectedSet; + } + dialog.setVisible(false); + } + } + else if(tabbedPane.getSelectedIndex() == 2) { + if(billsTable.getSelectedRow() == -1) { + JOptionPane.showMessageDialog(parent, Main.getString("viewColl_error_noSelectionMessage"), + Main.getString("viewColl_error_noSelectionTitle"), JOptionPane.ERROR_MESSAGE); + } + else { + idString = billsTable.getValueAt(billsTable.getSelectedRow(), 0); + // If didn't select empty row + if (idString != null) { + int id = Integer.parseInt(idString.toString()); + for (Bill bill : bills) + if (bill.getId() == id) + selectedItem = bill; + } + dialog.setVisible(false); + } + } + }; + OKButton.addActionListener(okListener); } public JPanel getPanel() { @@ -107,104 +247,127 @@ public JPanel getPanel() { } private void setSetsTable() { - String[] columnNames = {"ID", - "Name", - "Year", - "Coins", - "Face Value", - "Value", - "Note",}; + String[] columnNames = {Main.getString("property_set_id"), + Main.getString("property_set_year"), + Main.getString("property_set_name"), + Main.getString("property_set_items"), + Main.getString("property_set_faceValue"), + Main.getString("property_set_value"), + Main.getString("property_set_note"), + Main.getString("property_set_container")}; + + String[] totalsColumns = {Main.getString("property_set_totals"), + Main.getString("property_set_totals_sets"), + Main.getString("property_set_totals_items"), + Main.getString("property_set_totals_faceValue"), + Main.getString("property_set_totals_value")}; try { - sets = CoinSet.Companion.getSetsFromSql(((Main) parent).databaseConnection, ""); + sets = api.getSets(); + if(set != null) { + sets.remove(set); + + ArrayList setsInSet = new ArrayList<>(); + for (SetItem item : set.getItems()) { + if (item instanceof Set) + setsInSet.add((Set) item); + } + sets.removeAll(setsInSet); + } } catch (Exception e) { sets = new ArrayList<>(); } + DecimalFormat format = new DecimalFormat(); + format.applyPattern("0.00"); + // Show coins - Object[][] data = new Object[sets.size() + 1][columnNames.length]; + Object[][] data = new Object[sets.size()][columnNames.length]; + Object[][] totalsData = new Object[1][totalsColumns.length]; int count = 0; + double totalFaceValue = 0.0; + double totalValue = 0.0; + int itemCount = 0; + // for each set found - for (CoinSet set : sets) { + for (Set set : sets) { String columnData = ""; + // For each column for(int j = 0; j < columnNames.length; j++) { switch (j) { - case 0 -> columnData = "" + set.getId(); - case 1 -> columnData = set.getName(); - case 2 -> columnData = "" + set.getYear(); - case 3 -> columnData = "" + set.getCoins().size(); - case 4 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); + case 0: + columnData = "" + set.getId(); + break; + case 1: + columnData = "" + set.getYear(); + break; + case 2: + columnData = set.getName(); + break; + case 3: + // Add to total for all sets + itemCount += set.getItems().size(); - String value = format.format(set.getFaceValue()); + columnData = "" + set.getItems().size(); + break; + case 4: + totalFaceValue += set.getFaceValue(); - columnData = "" + value; - } - case 5 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); + columnData = format.format(set.getFaceValue()); + break; + case 5: + double value; - String value = format.format(set.getValue()); + // If no value set, use face value + if(set.getValue() == 0.0) + value = set.getFaceValue(); + else + value = set.getValue(); - columnData = "" + value; - } - case 6 -> columnData = set.getNote(); + totalValue += value; + + columnData = format.format(value); + break; + case 6: + columnData = set.getNote(); + break; + case 7: + columnData = api.findContainer(set.getContainerId()).getName(); + break; } data[count][j] = columnData; } count++; } - - data[count][1] = "Sets: " + sets.size() + ""; - - int coinCount = 0; - - for(CoinSet set : sets) - { - coinCount += set.getCoins().size(); - } - data[count][3] = "" + coinCount + ""; - - double totalFaceValue = 0.0; - for(CoinSet set : sets) { - totalFaceValue += set.getFaceValue(); - } - - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); - - String value = format.format(totalFaceValue); - - data[count][4] = "" + value + ""; - - // Add total current value - double totalValue = 0.0; - for(CoinSet set : sets) { - totalValue += set.getValue(); - } - - value = format.format(totalValue); - - data[count][5] = "" + value + ""; - - for(int i = 0; i < columnNames.length; i++) - if(data[count][i] == null || data[count][i].equals("")) - data[count][i] = " "; + // Put totals in the final row + totalsData[0][0] = "" + Main.getString("property_set_totals") + ""; + totalsData[0][1] = "" + MessageFormat.format(Main.getString("property_set_totals_sets"), + sets.size()) + ""; + totalsData[0][2] = "" + MessageFormat.format(Main.getString("property_set_totals_items"), + itemCount) + ""; + totalsData[0][3] = "" + MessageFormat.format(Main.getString("property_set_totals_faceValue"), + format.format(totalFaceValue)) + ""; + totalsData[0][4] = "" + MessageFormat.format(Main.getString("property_set_totals_value"), + format.format(totalValue)) + ""; // Set data for table DefaultTableModel dataModel = new DefaultTableModel(); dataModel.setDataVector(data, columnNames); setsTable.setModel(dataModel); + DefaultTableModel totalDataModel = new DefaultTableModel(); + totalDataModel.setDataVector(totalsData, totalsColumns); + setsTotalTable.setModel(totalDataModel); + // Make table non-editable, and single selection setsTable.setDefaultEditor(Object.class, null); setsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setsTotalTable.setEnabled(false); // Add right click listener setsTable.addMouseListener(new MouseAdapter() { @@ -220,12 +383,12 @@ public void mouseClicked(MouseEvent e) { if(index != -1 ) { // Don't error out if totals row selected - CoinSet selectedSet = null; + Set selectedSet = null; try { // Find the ID of the set that has been clicked int ID = Integer.parseInt((String)setsTable.getValueAt(index, 0)); // Find the set that has been clicked - for(CoinSet set : sets) + for(Set set : sets) if(set.getId() == ID) { selectedSet = set; break; @@ -235,35 +398,65 @@ public void mouseClicked(MouseEvent e) { return; } - final CoinSet finalSet = selectedSet; + if(selectedSet == null) + return; + + final Set finalSet = selectedSet; // Create right click menu final JPopupMenu setListRightClickMenu = new JPopupMenu(); - JMenuItem copy = new JMenuItem("Copy"); + JMenuItem copy = new JMenuItem(Main.getString("viewColl_rtClick_copySet")); // Create copy item copy.addActionListener(e1 -> { // Show edit set screen - AddSetScreen addSetScreen = new AddSetScreen(parent, finalSet.copy()); + AddSetScreen addSetScreen; + addSetScreen = new AddSetScreen(parent, finalSet.copy()); addSetScreen.setFromCollection(true); - ((Main) parent).changeScreen(addSetScreen.getPanel(), "New Set"); + ((Main) parent).changeScreen(addSetScreen.getPanel(), Main.getString("addSet_title_add")); }); - JMenuItem editSet = new JMenuItem("Edit"); + JMenuItem editSet = new JMenuItem(Main.getString("viewColl_rtClick_editSet")); editSet.addActionListener(e1 -> { // Show edit set screen AddSetScreen addSetScreen = new AddSetScreen(parent, finalSet); + String name; + if(finalSet.getName().equals("")) + name = Main.getString("property_set_toString"); + else + name = finalSet.getName(); + addSetScreen.setFromCollection(true); - ((Main) parent).changeScreen(addSetScreen.getPanel(), "Edit Set"); + ((Main) parent).changeScreen(addSetScreen.getPanel(), MessageFormat.format(Main.getString("addSet_title_edit"), name)); }); - JMenuItem removeSet = new JMenuItem("Remove from Collection"); + JMenuItem removeSet = new JMenuItem(Main.getString("viewColl_rtClick_removeSet")); removeSet.addActionListener(e1 ->{ // Show an "Are you sure?" prompt - int option = JOptionPane.showConfirmDialog(parent, "Are you sure you want to remove this set?", "Delete Set", JOptionPane.YES_NO_OPTION); + int option = JOptionPane.showConfirmDialog(parent, + Main.getString("viewColl_dialog_removeSetMessage"), + Main.getString("viewColl_dialog_removeSetTitle"), + JOptionPane.YES_NO_OPTION); + String errorMessage = ""; if(option == JOptionPane.YES_OPTION) { - finalSet.deleteFromDb(((Main)parent).databaseConnection); + try { + if(finalSet.removeFromDb(api)) { + if (!finalSet.deleteObvImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalSet.getObvImgPath()); + } + if (!finalSet.deleteRevImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalSet.getRevImgPath()); + } + } + } catch (SecurityException | IOException | SQLException securityException) { + errorMessage += securityException.getMessage(); + } + + if(!errorMessage.equals("")) { + JOptionPane.showMessageDialog(parent, errorMessage, + Main.getString("error_genericTitle"), JOptionPane.ERROR_MESSAGE); + } setSetsTable(); } }); @@ -278,102 +471,201 @@ public void mouseClicked(MouseEvent e) { } }); - addTableSort(setsTable, new ArrayList<>(List.of(0, 3, 4, 5))); - hideTableColumn(setsTable, "ID"); + setsTable.addSort(new ArrayList<>(List.of(0, 1, 3)), new ArrayList<>(List.of(4, 5))); + setsTable.hideColumn(Main.getString("property_set_id")); } private void setCoinsTable() { - String[] columnNames = {"ID", - "Coin", - "Denomination", - "Value", - "Country", - "Grade", - "Error", - "Note",}; + + String[] columnNames = {Main.getString("property_coin_id"), + Main.getString("property_coin_toString"), + Main.getString("property_coin_denomination"), + Main.getString("property_coin_value"), + Main.getString("property_coin_country"), + Main.getString("property_coin_grade"), + Main.getString("property_coin_error"), + Main.getString("property_coin_note"), + Main.getString("property_coin_container")}; + + String[] totalColumns = {Main.getString("property_coin_totals"), + Main.getString("property_coin_totals_coins"), + Main.getString("property_coin_totals_faceValue"), + Main.getString("property_coin_totals_value")}; try { - coins = Coin.Companion.getCoinsFromSql(((Main) parent).databaseConnection, ""); + coins = api.getCoins(); + if(set != null) { + ArrayList coinsInSet = new ArrayList<>(); + for (SetItem item : set.getItems()) { + if (item instanceof Coin) + coinsInSet.add((Coin) item); + } + coins.removeAll(coinsInSet); + } } catch (Exception ex) { coins = new ArrayList<>(); } + // For formatting the numbers + DecimalFormat format = new DecimalFormat(); + format.applyPattern("0.00"); + + DecimalFormat halfCentFormat = new DecimalFormat(); + halfCentFormat.applyPattern("0.000"); + // Show coins - Object[][] data = new Object[coins.size()+1][columnNames.length]; + Object[][] data = new Object[coins.size()][columnNames.length]; + Object[][] totalData = new Object[1][totalColumns.length]; + + double totalFaceValue = 0.0; + double totalValue = 0.0; + + boolean halfCentIncluded = false; int count = 0; // for each coin found for (Coin coin : coins) { + // Format denomination with currency symbol + Currency currency = coin.getCurrency(); + // For each column for(int j = 0; j < columnNames.length; j++) { String columnData = ""; - switch (j) { - case 0 -> columnData = "" + coin.getId(); - case 1 -> columnData = coin.toString(); - case 2 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); - String value = format.format(coin.getDenomination()); + String string; + Double value; + String formattedValue; - columnData = "" + value; - } - case 3 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); - - String value = format.format(coin.getValue()); + switch (j) { + case 0: + columnData = "" + coin.getId(); + break; + // Coin label - Year, Mint Marl, Type + case 1: + columnData = coin.toString(); + break; + // Denomination + case 2: - columnData = "" + value; - } - case 4 -> columnData = coin.getCountry(); - case 5 -> columnData = coin.getCondition(); - case 6 -> columnData = coin.getErrorType(); - case 7 -> columnData = coin.getNote(); - } - data[count][j] = columnData; - } - count++; - } + if(coin.getDenomination() == Coin.HALF_PENNY) { + formattedValue = halfCentFormat.format(coin.getDenomination()); + halfCentIncluded = true; + } + else + formattedValue = format.format(coin.getDenomination()); - data[count][1] = "Coins: " + coins.size() + ""; + totalFaceValue += coin.getDenomination(); - double totalFaceValue = 0.0; - for(Coin coin : coins) { - totalFaceValue += coin.getDenomination(); - } + if(currency.getSymbolBefore()) + string = currency.getSymbol() + formattedValue; + else + string = formattedValue + currency.getSymbol(); - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); + columnData = string; + break; + // Value + case 3: - String value = format.format(totalFaceValue); + if(coin.getValue() == 0.0) { + value = coin.getDenomination(); - data[count][2] = "" + value + ""; + if(coin.getDenomination() == Coin.HALF_PENNY) { + formattedValue = halfCentFormat.format(value); + } + else { + formattedValue = format.format(value); + } + } + else { + value = coin.getValue(); + formattedValue = format.format(value); + } - // Add total current value - double totalValue = 0.0; - for(Coin coin : coins) { - totalValue += coin.getValue(); - } + totalValue += value; - value = format.format(totalValue); + if(currency.getSymbolBefore()) + string = currency.getSymbol() + formattedValue; + else + string = formattedValue + currency.getSymbol(); - data[count][3] = "" + value + ""; + columnData = string; + break; + case 4: + columnData = coin.getCountryName(); + break; + case 5: + columnData = coin.getCondition(); + break; + case 6: + columnData = coin.getErrorType(); + break; + case 7: + columnData = coin.getNote(); + break; + case 8: + columnData = api.findContainer(coin.getContainerId()).getName(); + break; + } + data[count][j] = columnData; + } + count++; + } - for(int i = 0; i < columnNames.length; i++) - if(data[count][i] == null || data[count][i].equals("")) - data[count][i] = " "; + // If collection includes half cents, add 3rd point of precision + String faceValue; + if(halfCentIncluded) + faceValue = halfCentFormat.format(totalFaceValue); + else + faceValue = format.format(totalFaceValue); + + totalData[0][0] = "" + Main.getString("property_coin_totals") + ""; + totalData[0][1] = "" + MessageFormat.format(Main.getString("property_coin_totals_coins"), + coins.size()) + ""; + totalData[0][2] = "" + MessageFormat.format(Main.getString("property_coin_totals_faceValue"), + faceValue) + ""; + totalData[0][3] = "" + MessageFormat.format(Main.getString("property_coin_totals_value"), + format.format(totalValue)) + ""; // Set data for table DefaultTableModel dataModel = new DefaultTableModel(); dataModel.setDataVector(data, columnNames); coinsTable.setModel(dataModel); + DefaultTableModel totalDataModel = new DefaultTableModel(); + totalDataModel.setDataVector(totalData, totalColumns); + coinsTotalTable.setModel(totalDataModel); + // Make table non-editable, and single selection coinsTable.setDefaultEditor(Object.class, null); coinsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + coinsTotalTable.setEnabled(false); + + // TODO: Allow showing and hiding of columns - but do so in MyTable + /*coinsTable.getTableHeader().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if(SwingUtilities.isRightMouseButton(e)) { + //int column = coinsTable.columnAtPoint(e.getPoint()); + + // Create right click menu + final JPopupMenu coinHeaderRightClickMenu = new JPopupMenu(); + + // Add select columns item + JMenuItem headings = new JMenuItem("Select Columns"); + headings.addActionListener( e1 -> { + + }); + + coinHeaderRightClickMenu.add(headings); + + coinHeaderRightClickMenu.show(coinsTable.getTableHeader(), e.getPoint().x, e.getPoint().y); + } + else + super.mouseClicked(e); + } + });*/ // Add right click listener coinsTable.addMouseListener(new MouseAdapter() { @@ -403,37 +695,58 @@ public void mouseClicked(MouseEvent e) { return; } + if(selectedCoin == null) + return; + final Coin finalCoin = selectedCoin; // Create right click menu final JPopupMenu coinListRightClickMenu = new JPopupMenu(); // Add copy item - JMenuItem copy = new JMenuItem("Copy"); + JMenuItem copy = new JMenuItem(Main.getString("viewColl_rtClick_copyCoin")); copy.addActionListener( e1 -> { // Show edit coin screen - AddCoinScreen addCoinScreen = new AddCoinScreen(parent); + AddCoinScreen addCoinScreen = new AddCoinScreen(parent, finalCoin.copy()); - addCoinScreen.setCoin(finalCoin.copy()); addCoinScreen.setFromCollection(true); - ((Main) parent).changeScreen(addCoinScreen.getPanel(), "New Coin"); + ((Main) parent).changeScreen(addCoinScreen.getPanel(), Main.getString("addCoin_title_add")); }); - JMenuItem editCoin = new JMenuItem("Edit"); + JMenuItem editCoin = new JMenuItem(Main.getString("viewColl_rtClick_editCoin")); editCoin.addActionListener(e1 -> { // Show edit coin screen - AddCoinScreen addCoinScreen = new AddCoinScreen(parent); + AddCoinScreen addCoinScreen = new AddCoinScreen(parent, finalCoin); - addCoinScreen.setCoin(finalCoin); addCoinScreen.setFromCollection(true); - ((Main) parent).changeScreen(addCoinScreen.getPanel(), "Edit Coin"); + ((Main) parent).changeScreen(addCoinScreen.getPanel(), Main.getString("addCoin_title_edit")); }); - JMenuItem removeCoin = new JMenuItem("Remove from Collection"); + JMenuItem removeCoin = new JMenuItem(Main.getString("viewColl_rtClick_removeCoin")); removeCoin.addActionListener(e1 ->{ // Show an "Are you sure?" prompt - int option = JOptionPane.showConfirmDialog(parent, "Are you sure you want to remove this coin?", "Delete Coin", JOptionPane.YES_NO_OPTION); + int option = JOptionPane.showConfirmDialog(parent, + Main.getString("viewColl_dialog_removeCoinMessage"), + Main.getString("viewColl_dialog_removeCoinTitle"), + JOptionPane.YES_NO_OPTION); + String errorMessage = ""; if(option == JOptionPane.YES_OPTION) { - finalCoin.deleteFromDb(((Main)parent).databaseConnection); + try { + if(finalCoin.removeFromDb(api)) { + if (!finalCoin.deleteObvImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalCoin.getObvImgPath()); + } + if (!finalCoin.deleteRevImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalCoin.getRevImgPath()); + } + } + } catch (SecurityException | IOException | SQLException securityException) { + errorMessage += securityException.getMessage(); + } + + if(!errorMessage.equals("")) { + JOptionPane.showMessageDialog(parent, errorMessage, + Main.getString("error_genericTitle"), JOptionPane.ERROR_MESSAGE); + } setCoinsTable(); } }); @@ -448,107 +761,162 @@ public void mouseClicked(MouseEvent e) { } }); - addTableSort(coinsTable, new ArrayList<>(List.of(0, 2,3))); - hideTableColumn(coinsTable, "ID"); + coinsTable.addSort(new ArrayList<>(List.of(0)), new ArrayList<>(List.of(2,3))); + coinsTable.hideColumn(Main.getString("property_coin_id")); } private void setBillsTable() { - String[] columnNames = {"ID", - "Bill", - "Denomination", - "Value", - "Country", - "Signatures", - "Grade", - "Error", - "Star", - "Note",}; + String[] columnNames = {Main.getString("property_bill_id"), + Main.getString("property_bill_toString"), + Main.getString("property_bill_denomination"), + Main.getString("property_bill_value"), + Main.getString("property_bill_country"), + Main.getString("property_bill_serial"), + Main.getString("property_bill_signatures"), + Main.getString("property_bill_grade"), + Main.getString("property_bill_error"), + Main.getString("property_bill_replacement"), + Main.getString("property_bill_note"), + Main.getString("property_bill_container")}; + + String[] totalColumns = {Main.getString("property_bill_totals"), + Main.getString("property_bill_totals_bills"), + Main.getString("property_bill_totals_faceValue"), + Main.getString("property_bill_totals_value"), + Main.getString("property_bill_totals_replacements")}; try { - bills = Bill.Companion.getBillsFromSql(((Main) parent).databaseConnection, ""); + bills = api.getBills(); + if(set != null) { + ArrayList billsInSet = new ArrayList<>(); + for (SetItem item : set.getItems()) { + if (item instanceof Bill) + billsInSet.add((Bill) item); + } + bills.removeAll(billsInSet); + } } catch (Exception e) { bills = new ArrayList<>(); } + // For formatting the numbers + DecimalFormat format = new DecimalFormat(); + format.applyPattern("0.00"); + // Show bills - Object[][] data = new Object[bills.size()+1][columnNames.length]; + Object[][] data = new Object[bills.size()][columnNames.length]; + Object[][] totalData = new Object[1][totalColumns.length]; int count = 0; + + double totalFaceValue = 0.0; + double totalValue = 0.0; + int totalReplacements = 0; + // for each bill found for (Bill bill : bills) { + // Format denomination with currency symbol + Currency currency = api.findCurrency(bill.getCurrency().getNameAbbr()); + + double value; + // For each column for(int j = 0; j < columnNames.length; j++) { String columnData = ""; switch (j) { - case 0 -> columnData += bill.getId(); - case 1 -> columnData = bill.toString(); - case 2 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); + case 0: + columnData += bill.getId(); + break; + case 1: + columnData = bill.toString(); + break; + case 2: + String valueString = format.format(bill.getDenomination()); + totalFaceValue += bill.getDenomination(); + + if(currency.getSymbolBefore()) + valueString = currency.getSymbol() + valueString; + else + valueString = valueString + currency.getSymbol(); + columnData = "" + valueString; + break; + case 3: + String string; - String value = format.format(bill.getDenomination()); + if(bill.getValue() == 0.0) + value = bill.getDenomination(); + else + value = bill.getValue(); - columnData = "" + value; - } - case 3 -> { - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); + totalValue += value; - String value = format.format(bill.getValue()); + if(currency.getSymbolBefore()) + string = currency.getSymbol() + format.format(value); + else + string = format.format(value) + currency.getSymbol(); - columnData = "" + value; - } - case 4 -> columnData = bill.getCountry(); - case 5 -> columnData = bill.getSignatures(); - case 6 -> columnData = bill.getCondition(); - case 7 -> columnData = bill.getErrorType(); - case 8 -> columnData = "" + bill.getStar(); - case 9 -> columnData = bill.getNote(); + columnData = "" + string; + break; + case 4: + columnData = bill.getCountryName(); + break; + case 5: + columnData = bill.getSerial(); + break; + case 6: + columnData = bill.getSignatures(); + break; + case 7: + columnData = bill.getCondition(); + break; + case 8: + columnData = bill.getErrorType(); + break; + case 9: + + if(bill.getReplacement()) { + totalReplacements++; + columnData = "X"; + } + break; + case 10: + columnData = bill.getNote(); + break; + case 11: + columnData = api.findContainer(bill.getContainerId()).getName(); + break; } data[count][j] = columnData; } count++; } - data[count][1] = "Bills: " + bills.size() + ""; - - double totalFaceValue = 0.0; - for(Bill bill : bills) { - totalFaceValue += bill.getDenomination(); - } - - DecimalFormat format = new DecimalFormat(); - format.applyPattern("0.00"); - - String value = format.format(totalFaceValue); - - data[count][2] = "" + value + ""; - - // Add total current value - double totalValue = 0.0; - for(Bill bill : bills) { - totalValue += bill.getValue(); - } - - value = format.format(totalValue); - - data[count][3] = "" + value + ""; - - // Put space in all empty columns in totals row - for(int i = 0; i < columnNames.length; i++) - if(data[count][i] == null || data[count][i].equals("")) - data[count][i] = " "; + // Show totals + totalData[0][0] = "" + Main.getString("property_bill_totals") + ""; + totalData[0][1] = "" + MessageFormat.format(Main.getString("property_bill_totals_bills"), + bills.size()) + ""; + totalData[0][2] = "" + MessageFormat.format(Main.getString("property_bill_totals_replacements"), + totalReplacements) + ""; + totalData[0][3] = "" + MessageFormat.format(Main.getString("property_bill_totals_faceValue"), + format.format(totalFaceValue)) + ""; + totalData[0][4] = "" + MessageFormat.format(Main.getString("property_bill_totals_value"), + format.format(totalValue)) + ""; // Set data for table DefaultTableModel dataModel = new DefaultTableModel(); dataModel.setDataVector(data, columnNames); billsTable.setModel(dataModel); + DefaultTableModel totalDataModel = new DefaultTableModel(); + totalDataModel.setDataVector(totalData, totalColumns); + billsTotalTable.setModel(totalDataModel); + // Make table non-editable, and single selection billsTable.setDefaultEditor(Object.class, null); billsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + billsTotalTable.setEnabled(false); // Add right click listener billsTable.addMouseListener(new MouseAdapter() { @@ -578,36 +946,58 @@ public void mouseClicked(MouseEvent e) { return; } + if(selectedBill == null) + return; + final Bill finalBill = selectedBill; // Create right click menu final JPopupMenu billListRightClickMenu = new JPopupMenu(); - JMenuItem copy = new JMenuItem("Copy"); + JMenuItem copy = new JMenuItem(Main.getString("viewColl_rtClick_copyBill")); copy.addActionListener(e1 -> { // Show edit bill screen - AddBillScreen addBillScreen = new AddBillScreen(parent); + AddBillScreen addBillScreen = new AddBillScreen(parent, finalBill.copy()); - addBillScreen.setBill(finalBill.copy()); addBillScreen.setFromCollection(true); - ((Main) parent).changeScreen(addBillScreen.getPanel(), "Edit Bill"); + ((Main) parent).changeScreen(addBillScreen.getPanel(), Main.getString("addBill_title_add")); }); - JMenuItem editBill = new JMenuItem("Edit"); + JMenuItem editBill = new JMenuItem(Main.getString("viewColl_rtClick_editBill")); editBill.addActionListener(e1 -> { // Show edit bill screen - AddBillScreen addBillScreen = new AddBillScreen(parent); + AddBillScreen addBillScreen = new AddBillScreen(parent, finalBill); - addBillScreen.setBill(finalBill); addBillScreen.setFromCollection(true); - ((Main) parent).changeScreen(addBillScreen.getPanel(), "Edit Bill"); + ((Main) parent).changeScreen(addBillScreen.getPanel(), Main.getString("addBill_title_edit")); }); - JMenuItem removeBill = new JMenuItem("Remove from Collection"); + JMenuItem removeBill = new JMenuItem(Main.getString("viewColl_rtClick_removeBill")); removeBill.addActionListener(e1 ->{ // Show an "Are you sure?" prompt - int option = JOptionPane.showConfirmDialog(parent, "Are you sure you want to remove this bill?", "Delete Bill", JOptionPane.YES_NO_OPTION); + int option = JOptionPane.showConfirmDialog(parent, + Main.getString("viewColl_dialog_removeBillMessage"), + Main.getString("viewColl_dialog_removeBillTitle"), + JOptionPane.YES_NO_OPTION); if(option == JOptionPane.YES_OPTION) { - finalBill.deleteFromDb(((Main)parent).databaseConnection); + String errorMessage = ""; + try { + if(finalBill.removeFromDb(api)) { + if (!finalBill.deleteObvImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalBill.getObvImgPath()); + } + if (!finalBill.deleteRevImage()) { + errorMessage += MessageFormat.format(Main.getString("error_deletingFile_message"), finalBill.getRevImgPath()); + } + } + } catch (SecurityException | IOException | SQLException securityException) { + errorMessage += securityException.getMessage(); + } + + if(!errorMessage.equals("")) { + JOptionPane.showMessageDialog(parent, errorMessage, + Main.getString("error_genericTitle"), JOptionPane.ERROR_MESSAGE); + } + setBillsTable(); } }); @@ -622,8 +1012,8 @@ public void mouseClicked(MouseEvent e) { } }); - addTableSort(billsTable, new ArrayList<>(List.of(0, 2, 3))); - hideTableColumn(billsTable, "ID"); + billsTable.addSort(new ArrayList<>(List.of(0)), new ArrayList<>(List.of(2,3))); + billsTable.hideColumn(Main.getString("property_bill_id")); } private void goHome() { @@ -632,90 +1022,103 @@ private void goHome() { private void export(String path) { - try{ - TableModel model; + SwingWorker worker = new SwingWorker<>() { - switch (tabbedPane.getSelectedIndex()) { - case 0 -> model = coinsTable.getModel(); - case 1 -> model = setsTable.getModel(); - default -> model = billsTable.getModel(); - } + String error = ""; - File file = new File(path); - file.createNewFile(); // if file already exists will do nothing + @Override + protected Void doInBackground(){ + try{ + TableModel model; + JTable table; - FileWriter excel = new FileWriter(file); + switch (tabbedPane.getSelectedIndex()) { + case 0: table = coinsTable; + break; + case 1: table = setsTable; + break; + default: table = billsTable; + break; + } - for (int i = 0; i < model.getColumnCount(); i++) { - excel.write(model.getColumnName(i) + "\t"); - } + model = table.getModel(); + + File file = new File(path); - excel.write("\n"); + // If file already exists + if(!file.createNewFile()) { + int result = JOptionPane.showConfirmDialog(parent, + Main.getString("warning_file_exists_message"), + path,JOptionPane.YES_NO_OPTION); + if(result == JOptionPane.NO_OPTION) { + error = Main.getString("warning_file_write_cancelled"); + return null; + } + } + + FileWriter csv = new FileWriter(file); + + // Output column names + for (int i = 0; i < model.getColumnCount(); i++) { + // Don't output hidden columns + if(table.getColumn(table.getColumnName(i)).getWidth() != 0) { + // Using getModelIndex in case columns were moved + csv.write(model.getColumnName(table.getColumn(table.getColumnName(i)).getModelIndex()) + ","); + } + } + + csv.write("\n"); + + // Output data 1 row at a time + for (int i = 0; i < model.getRowCount(); i++) { + for (int j = 0; j < model.getColumnCount(); j++) { + // Don't output hidden columns + if(table.getColumn(table.getColumnName(j)).getWidth() != 0) { + if (model.getValueAt(i, j) != null) + // Using getModelIndex in case columns were moved + csv.write(model.getValueAt(i, table.getColumn(table.getColumnName(j)).getModelIndex()) + ","); + else + csv.write(","); + } + } + csv.write("\n"); + } + + csv.close(); - // Don't include the totals row - for (int i = 0; i < model.getRowCount() -1; i++) { - for (int j = 0; j < model.getColumnCount(); j++) { - if(model.getValueAt(i, j) != null) - excel.write(model.getValueAt(i, j).toString() + "\t"); - else - excel.write("\t"); + } catch(IOException e){ + error = e.getMessage(); } - excel.write("\n"); + return null; } - excel.close(); + @Override + protected void done() { + if(!error.equals("")) { + JOptionPane.showMessageDialog(parent, error, Main.getString("error_fileSave"), JOptionPane.ERROR_MESSAGE); + } + } + }; - } catch(IOException e){ - e.printStackTrace(); - } + Main.showBackgroundPopup(parent, + Main.getString("export_file_saving_message"), + Main.getString("export_file_saving_title"), worker); } public void setTab(int index) { tabbedPane.setSelectedIndex(index); } - private void hideTableColumn(JTable table, String columnName) { - table.getColumn(columnName).setMinWidth(0); // Must be set before maxWidth!! - table.getColumn(columnName).setMaxWidth(0); - table.getColumn(columnName).setWidth(0); - } - - private void addTableSort(JTable table, ArrayList numberColumns) { - - TableRowSorter sorter = new TableRowSorter(); - table.setRowSorter(sorter); - sorter.setModel(table.getModel()); - - ArrayList textColumns = new ArrayList<>(); - textColumns.add(1); - // Add all non-number rows to the list of text rows - for(int i = 0; i < table.getColumnCount(); i++) - if(!numberColumns.contains(i)) - textColumns.add(i); + public void setSet(Set set) { + this.set = set; - // Number based columns - for(int num : numberColumns) { - sorter.setComparator(num, (Comparator) (name1, name2) -> { - try { - double one = Double.parseDouble(name1); - double two = Double.parseDouble(name2); - - return Double.compare(one, two); - } catch (NumberFormatException e) { - return 0; - } - }); - } + // Remove this set, and any items in the set, from the list + setCoinsTable(); + setBillsTable(); + setSetsTable(); + } - // Text based columns - for (int num : textColumns) { - sorter.setComparator(num, (Comparator) (name1, name2) -> { - if (name1.equals(" ") || name2.equals(" ") || - name1.startsWith("") || name2.startsWith("")) - return 0; - else - return name1.compareTo(name2); - }); - } + public Set getSet() { + return set; } } diff --git a/src/ComboBoxHelper.java b/src/ComboBoxHelper.java new file mode 100644 index 0000000..f34c652 --- /dev/null +++ b/src/ComboBoxHelper.java @@ -0,0 +1,201 @@ +import items.Book; +import items.Container; +import items.Country; +import items.Currency; + +import javax.swing.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.*; + +public class ComboBoxHelper { + + public static void setupBoxes(AutoComboBox countryInput, JTextField yearInput, AutoComboBox currencyInput, JTextArea errorDisplay, NumismatistAPI api) { + + setKeyWord(countryInput, api.getCountries()); + + // set currency when country changes + countryInput.addActionListener(e -> { + ComboBoxHelper.setCurrency(countryInput, yearInput, currencyInput, api.getCountries()); + }); + + yearInput.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + + } + + @Override + public void focusLost(FocusEvent e) { + try { + ComboBoxHelper.setCurrency(countryInput, yearInput, currencyInput, api.getCountries()); + errorDisplay.setText(""); + } + catch (NumberFormatException nfe) { + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(Main.getString("error_invalidDate")); + } + } + }); + + yearInput.addActionListener(e -> { + try { + ComboBoxHelper.setCurrency(countryInput, yearInput, currencyInput, api.getCountries()); + errorDisplay.setText(""); + } + catch (NumberFormatException nfe) { + errorDisplay.setForeground(Main.COLOR_ERROR); + errorDisplay.setText(Main.getString("error_invalidDate")); + } + }); + + countryInput.setSelectedIndex(0); + } + + public static void setCurrency(AutoComboBox countryInput, JTextField yearInput, AutoComboBox currencyInput, ArrayList countries) throws NumberFormatException { + int thisYear = Calendar.getInstance().get(Calendar.YEAR); + + JTextField countryBox = (JTextField) countryInput.getEditor().getEditorComponent(); + + String startingSelection = ""; + if(currencyInput.getSelectedItem() != null) { + startingSelection = currencyInput.getSelectedItem().toString(); + } + + NumberFormatException numberFormatException = null; + + ArrayList allCurrencies = new ArrayList<>(); + ArrayList validCurrencies = new ArrayList<>(); + ArrayList validIds = new ArrayList<>(); + + for (Country country : countries) { + if(country.getName().equals(countryBox.getText())) { + allCurrencies = country.getCurrencies(); + break; + } + } + + if(allCurrencies.isEmpty()) { + ((JTextField)currencyInput.getEditor().getEditorComponent()).setText(""); + setKeyWord(currencyInput, new ArrayList()); + } + else { + for(items.Currency currency : allCurrencies) { + int start = currency.getYrStart(); + int end = currency.getYrEnd(); + + int input = thisYear; + + // Get year from input box + if (yearInput.getText() != null && !yearInput.getText().isBlank()) + { + try { + input = Integer.parseInt(yearInput.getText()); + } catch (NumberFormatException nfe) { + numberFormatException = nfe; + } + } + + // If invalid input or if year input is empty, show all valid currencies for this country + if(numberFormatException != null || yearInput.getText() == null || yearInput.getText().isBlank()) { + validCurrencies.add(currency); + validIds.add(currency.getNameAbbr()); + } + // if year is valid, Check if this currency is valid for this time period + else if( (start <= input || start == Currency.YEAR_START_INVALID) && (input <= end || end == Currency.YEAR_END_INVALID) ) { + validCurrencies.add(currency); + validIds.add(currency.getNameAbbr()); + } + } + + // If no valid currencies for this country / year combination, + // display all valid currencies for this country + if(validCurrencies.isEmpty()) { + validCurrencies = allCurrencies; + + // Add valid IDs too + // Clear it first, just in case + validIds.clear(); + for(items.Currency newCurrency : allCurrencies) { + validIds.add(newCurrency.getNameAbbr()); + } + } + + setKeyWord(currencyInput, validCurrencies); + //currencyInput.setIds(validIds.toArray(new String[0])); + } + + // If current selection no longer valid + if(!Arrays.asList(currencyInput.keyWord).contains(startingSelection)) { + // Select first in the list + if(currencyInput.getItemCount() > 0) + currencyInput.setSelectedIndex(0); + // Clear the box if list is empty + else + ((JTextField) currencyInput.getEditor().getEditorComponent()).setText(""); + } + // Reselect old selection if index has changed + else if(currencyInput.getItemCount() > 0) + currencyInput.setSelectedIndex(Arrays.asList(currencyInput.keyWord).indexOf(startingSelection)); + + if(numberFormatException != null) + throw numberFormatException; + } + + public static void setContainerList(JComboBox comboBox, NumismatistAPI api) { + setContainerList(comboBox, api, null, null); + } + + public static void setContainerList(JComboBox comboBox, NumismatistAPI api, JButton addButton, JFrame parent) { + Vector items = new Vector<>(); + items.add(""); + for(Container container : api.getContainers() ) { + items.add(container.getName()); + } + + SortedComboBoxModel model = new SortedComboBoxModel(items); + comboBox.setModel(model); + + if(addButton != null && parent != null) { + addButton.addActionListener(e -> { + NewContainerDialog newContainerDialog = new NewContainerDialog(parent); + newContainerDialog.pack(); + newContainerDialog.setLocationRelativeTo(parent); + newContainerDialog.setLocationsBox(comboBox); + newContainerDialog.setVisible(true); + }); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void setKeyWord(AutoComboBox box, ArrayList list) { + ArrayList newList = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); + + if(!list.isEmpty() && list.get(0) instanceof Country) { + ArrayList countryList = ((ArrayList)list); + for (Country country : countryList) { + newList.add(country.getName()); + ids.add(country.getName()); + } + box.setKeyWord(newList.toArray(String[]::new)); + box.ids = ids.toArray(new String[0]); + } + else if(!list.isEmpty() && list.get(0) instanceof items.Currency) { + ArrayList currencyList = ((ArrayList)list); + for (Currency currency : currencyList) { + newList.add(currency.getName()); + ids.add(currency.getNameAbbr()); + } + box.setKeyWord(newList.toArray(String[]::new)); + box.ids = ids.toArray(new String[0]); + } + else if(!list.isEmpty() && list.get(0) instanceof String) { + newList.addAll(((ArrayList) list)); + box.setKeyWord(newList.toArray(String[]::new)); + } + + DefaultComboBoxModel model = new DefaultComboBoxModel<>( new Vector<>(newList) ); + box.setModel(model); + } +} diff --git a/src/ComboListener.java b/src/ComboListener.java new file mode 100644 index 0000000..36ea230 --- /dev/null +++ b/src/ComboListener.java @@ -0,0 +1,133 @@ +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.Locale; +import java.util.Vector; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JTextField; + +public class ComboListener extends KeyAdapter +{ + @SuppressWarnings("rawtypes") + JComboBox cbListener; + @SuppressWarnings("rawtypes") + Vector vector; + + @SuppressWarnings("rawtypes") + public ComboListener(JComboBox cbListenerParam, Vector vectorParam) + { + cbListener = cbListenerParam; + vector = vectorParam; + } + + /** + * Called when any key is typed while cursor is in the ComboBox + * + * @param key - The key that was pressed + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void keyTyped(KeyEvent key) + { + boolean popupHidden = false; + + // Find any selected text + String selected = ((JTextField)key.getSource()).getSelectedText(); + + // Text currently in the ComboBox + String text = ((JTextField) key.getSource()).getText(); + + // If no selected text + if(selected == null) { + // Current cursor position + int pos = ((JTextField) key.getSource()).getCaretPosition(); + + // Let system handle backspace and delete... Just re-display text + if(key.getKeyChar() == KeyEvent.VK_BACK_SPACE || key.getKeyChar() == KeyEvent.VK_DELETE) { + cbListener.setModel(new DefaultComboBoxModel(getFilteredList(text))); + cbListener.setSelectedIndex(-1); + ((JTextField)cbListener.getEditor().getEditorComponent()).setText(text); + ((JTextField) key.getSource()).setCaretPosition(pos); + } + // For escape and enter, redisplay text and hide popup + else if(key.getKeyChar() == KeyEvent.VK_ESCAPE || key.getKeyChar() == KeyEvent.VK_ENTER) { + cbListener.setModel(new DefaultComboBoxModel(getFilteredList(text))); + cbListener.setSelectedIndex(-1); + ((JTextField)cbListener.getEditor().getEditorComponent()).setText(text); + ((JTextField) key.getSource()).setCaretPosition(pos); + cbListener.hidePopup(); + popupHidden = true; + key.consume(); + } + // for visible characters + else { + // If cursor is not at the end: add character at cursor position + if(pos != text.length()) { + StringBuilder sb = new StringBuilder(text); + sb.insert(pos, key.getKeyChar()); + cbListener.setModel(new DefaultComboBoxModel(getFilteredList(sb.toString()))); + ((JTextField) key.getSource()).setText(sb.toString()); + } + // If cursor is at the end of the text: add character to the end + else { + cbListener.setModel(new DefaultComboBoxModel(getFilteredList(text + key.getKeyChar()))); + cbListener.setSelectedIndex(-1); + ((JTextField) key.getSource()).setText(text + key.getKeyChar()); + } + + // Advance the cursor by 1 + ((JTextField) key.getSource()).setCaretPosition(pos+1); + + // Don't let system continue processing for visible characters + key.consume(); + } + } + // Handle selected text + else { + + // If escape or enter: hide popup + if(key.getKeyChar() == KeyEvent.VK_ESCAPE || key.getKeyChar() == KeyEvent.VK_ENTER) { + cbListener.hidePopup(); + popupHidden = true; + } + // If anything else, handle key press + else { + // Replace selected text with typed character + ((JTextField) key.getSource()).replaceSelection("" + key.getKeyChar()); + // Get new text + text = ((JTextField) key.getSource()).getText(); + } + + // Display new value + cbListener.setModel(new DefaultComboBoxModel(getFilteredList(text))); + cbListener.setSelectedIndex(-1); + ((JTextField)cbListener.getEditor().getEditorComponent()).setText(text); + + // Don't let system continue processing + key.consume(); + } + + // Remove any selection + cbListener.setSelectedIndex(-1); + if(!popupHidden) + cbListener.showPopup(); + } + + /** + * Filters the list of items in the ComboBox based on what is currently + * typed in the box + * + * @param text Text currently typed into the ComboBox + * @return List of filtered items to appear in the ComboBox list + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Vector getFilteredList(String text) + { + Vector v = new Vector(); + for (Object o : vector) { + if (o.toString().toLowerCase(Locale.ROOT).startsWith(text.toLowerCase(Locale.ROOT))) { + v.add(o.toString()); + } + } + return v; + } +} \ No newline at end of file diff --git a/src/CsvFilter.kt b/src/CsvFilter.kt new file mode 100644 index 0000000..6a4b7f6 --- /dev/null +++ b/src/CsvFilter.kt @@ -0,0 +1,46 @@ +import java.io.File +import javax.swing.filechooser.FileFilter + +object CsvExtensions { + + val ACCEPTABLE_EXTENSIONS = arrayOf("csv") + /* + * Get the extension of a file. + */ + fun getExtension(f: File): String? { + var ext: String? = null + val s = f.name + val i = s.lastIndexOf('.') + if (i > 0 && i < s.length - 1) { + ext = s.substring(i + 1).toLowerCase() + } + return ext + } +} + +class CsvFilter : FileFilter() { + override fun accept(f: File): Boolean { + if (f.isDirectory) { + return true + } + + val extension = CsvExtensions.getExtension(f) + return if (extension != null) { + CsvExtensions.ACCEPTABLE_EXTENSIONS.contains(extension) + } else false + } + + override fun getDescription(): String { + var string = "CSV File (" + + for((count, extension) in CsvExtensions.ACCEPTABLE_EXTENSIONS.withIndex()) { + if (count > 0) + string += ", " + string += "*.$extension" + } + + string += ")" + + return string + } +} \ No newline at end of file diff --git a/src/DoubleListDialog.form b/src/DoubleListDialog.form new file mode 100644 index 0000000..b5a618a --- /dev/null +++ b/src/DoubleListDialog.form @@ -0,0 +1,156 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/DoubleListDialog.java b/src/DoubleListDialog.java new file mode 100644 index 0000000..826936d --- /dev/null +++ b/src/DoubleListDialog.java @@ -0,0 +1,245 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.Arrays; + +@SuppressWarnings({"rawtypes", "unused"}) +public class DoubleListDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JList sourceList; + private JButton addButton; + private JButton removeButton; + private JList destList; + private JLabel sourceLabel; + private JLabel destLabel; + private JButton upButton; + private JButton downButton; + + private DefaultListModel source = new DefaultListModel(); + private DefaultListModel dest = new DefaultListModel(); + + private final ArrayList sourceArgs = new ArrayList<>(); + private final ArrayList destArgs = new ArrayList<>(); + + boolean cancelled = false; + + public DoubleListDialog(ArrayList source, ArrayList dest) { + setContentPane(contentPane); + setModal(true); + getRootPane().setDefaultButton(buttonOK); + setSize(new Dimension(500,400)); + setMinimumSize(new Dimension(500,400)); + + // Remove anything from the source that is already moved to the destination side + source.removeAll(dest); + + this.source.addAll(source); + this.dest.addAll(dest); + + setIconImage(Main.getIcon().getImage()); + + buttonOK.addActionListener(e -> onOK()); + buttonCancel.addActionListener(e -> onCancel()); + + sourceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + sourceList.setLayoutOrientation(JList.VERTICAL); + destList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + destList.setLayoutOrientation(JList.VERTICAL); + + getRootPane().setDefaultButton(buttonOK); + + addButton.addActionListener(e -> { + if(sourceList.getSelectedIndex()!=-1) { + Object selected = sourceList.getSelectedValue(); + this.source.remove(sourceList.getSelectedIndex()); + + // Add new item below selection, if possible + if(destList.getSelectedIndex()==-1) + this.dest.add(this.dest.size(), selected); + else + this.dest.add(destList.getSelectedIndex()+1, selected); + refreshLists(); + } + }); + + removeButton.addActionListener(e -> { + if(destList.getSelectedIndex()!=-1) { + Object selected = destList.getSelectedValue(); + this.dest.remove(destList.getSelectedIndex()); + this.source.add(0, selected); + refreshLists(); + } + }); + + upButton.addActionListener(e -> { + if(destList.getSelectedIndex()>0) { + int selected = destList.getSelectedIndex(); + Object selectedItem = destList.getSelectedValue(); + int above = selected-1; + Object aboveItem = this.dest.get(above); + this.dest.set(above,selectedItem); + this.dest.set(selected, aboveItem); + refreshLists(); + this.destList.setSelectedIndex(above); + } + }); + + downButton.addActionListener(e -> { + if(destList.getSelectedIndex()!=-1 && destList.getSelectedIndex() < this.dest.size()-1) { + int selected = destList.getSelectedIndex(); + Object selectedItem = destList.getSelectedValue(); + int below = selected+1; + Object belowItem = this.dest.get(below); + this.dest.set(below,selectedItem); + this.dest.set(selected, belowItem); + refreshLists(); + this.destList.setSelectedIndex(below); + } + }); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void refreshLists() { + + // Remove already added items + for(Object item : dest.toArray()) { + for(int i = 0; i < source.size(); i++) + if(source.get(i).equals(item)) + source.remove(i); + } + + // Always keep source sorted + Object[] sortedSource = Arrays.stream(source.toArray()).sorted().toArray(); + source.clear(); + source.addAll(Arrays.asList(sortedSource)); + + sourceList.setListData(source.toArray()); + destList.setListData(dest.toArray()); + } + + /** + * Set the method for the source object type to get something sortable. + * Ex: getName if there is a getName() function + * Ex: (getName, en-us) if there is a getName(Locale) function + * + * @param sourceMethod name of the method to use to turn this object into something sortable + * @param args arguments to use in the function + */ + public void setSourceMethod(String sourceMethod, Object ... args ) { + sourceArgs.addAll(Arrays.asList(args)); + + if(!sourceMethod.equals("")) { + ArrayList newSource = new ArrayList<>(); + + for(Object item : source.toArray()) { + try { + newSource.add(item.getClass().getMethod(sourceMethod).invoke(item, sourceArgs.toArray())); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + source.clear(); + source.addAll(newSource); + refreshLists(); + } + } + + /** + * Set the method for the destination object type to get something sortable. + * Ex: getName if there is a getName() function + * Ex: (getName, en-us) if there is a getName(Locale) function + * + * @param destMethod name of the method to use to turn this object into something sortable + * @param args arguments to use in the function + */ + public void setDestMethod(String destMethod, Object ... args) { + destArgs.addAll(Arrays.asList(args)); + + if(!destMethod.equals("")) { + ArrayList newDest = new ArrayList<>(); + + for(Object item : dest.toArray()) { + try { + newDest.add(item.getClass().getMethod(destMethod).invoke(item, destArgs.toArray())); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + dest.clear(); + dest.addAll(newDest); + refreshLists(); + } + } + + private void onOK() { + dispose(); + } + + private void onCancel() { + cancelled = true; + dispose(); + } + + /** + * Sets the label text for the source list + * + * @param name label text + */ + void setSourceName(String name) { + sourceLabel.setText(name); + } + + /** + * Sets the label text for the destination list + * + * @param name label text + */ + void setDestName(String name) { + destLabel.setText(name); + } + + private void createUIComponents() { + dest = new DefaultListModel(); + source = new DefaultListModel(); + + destList = new JList(dest); + sourceList = new JList(source); + } + + /** + * Gets an array of destination items + * + * @return An array of items in the destination column + */ + public Object[] getDest() { + return dest.toArray(); + } + + /** + * Gets an array of source items + * + * @return An array of items in the source column + */ + public Object[] getSource() { + return source.toArray(); + } +} diff --git a/src/ExcelFilter.kt b/src/ExcelFilter.kt index 218a939..52db902 100644 --- a/src/ExcelFilter.kt +++ b/src/ExcelFilter.kt @@ -19,8 +19,8 @@ object ExcelExtensions { } class ExcelFilter : FileFilter() { - override fun accept(f: File?): Boolean { - if (f!!.isDirectory) { + override fun accept(f: File): Boolean { + if (f.isDirectory) { return true } @@ -31,12 +31,12 @@ class ExcelFilter : FileFilter() { } override fun getDescription(): String { - var string = "Excel (" + var string = "Excel Files (" for((count, extension) in ExcelExtensions.ACCEPTABLE_EXTENSIONS.withIndex()) { if (count > 0) string += ", " - string += ".$extension" + string += "*.$extension" } string += ")" diff --git a/src/ExistingCoinDialog.java b/src/ExistingCoinDialog.java deleted file mode 100644 index 218ec30..0000000 --- a/src/ExistingCoinDialog.java +++ /dev/null @@ -1,139 +0,0 @@ - -import items.Coin; -import items.CoinSet; - -import javax.swing.*; -import javax.swing.table.DefaultTableModel; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; - -public class ExistingCoinDialog extends JDialog { - private JPanel contentPane; - private JButton buttonOK; - private JButton buttonCancel; - private JTable coinTable; - - private boolean cancelled = false; - private final ArrayList coins; - - public ExistingCoinDialog(JFrame parent, CoinSet set) { - super(parent); - setContentPane(contentPane); - setModal(true); - setTitle("Add coin to set"); - getRootPane().setDefaultButton(buttonOK); - setMinimumSize(new Dimension(400,300)); - - String[] columnNames = {"ID", - "Coin", - "Country", - "Grade", - "Error", - "Note",}; - - coins = Coin.Companion.getCoinsFromSql(((Main)parent).databaseConnection, ""); - - // Show coins - Object[][] data = new Object[coins.size()][columnNames.length]; - - int duplicates = 0; - - int count = 0; - // for each coin found - for (Coin coin : coins) { - - boolean duplicate = false; - // For each column - for(int j = 0; j < columnNames.length; j++) { - String columnData = ""; - switch (j) { - case 0 -> { - // Find coins that have already been added to the set - int id = coin.getId(); - for (int c = 0; c < set.getCoins().size(); c++) { - // If this is a duplicate, don't add it - if (set.getCoins().get(c).getId() == id) { - duplicate = true; - duplicates++; - } - } - columnData += coin.getId(); - } - case 1 -> columnData = coin.toString(); - case 2 -> columnData = coin.getCountry(); - case 3 -> columnData = coin.getCondition(); - case 4 -> columnData = coin.getErrorType(); - case 5 -> columnData = coin.getNote(); - } - if(!duplicate) - data[count-duplicates][j] = columnData; - } - count++; - } - - // Set data for table - DefaultTableModel dataModel = new DefaultTableModel(); - dataModel.setDataVector(data, columnNames); - coinTable.setModel(dataModel); - - // Remove empty rows, created due to already adding an existing coin - for(int i = 0; i < duplicates; i++) - ((DefaultTableModel)coinTable.getModel()).removeRow(coinTable.getModel().getRowCount()-1); - - // Make table non-editable, and single selection - coinTable.setDefaultEditor(Object.class, null); - coinTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - coinTable.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if(e.getClickCount() == 2) { - onOK(); - } - } - }); - - buttonOK.addActionListener(e -> onOK()); - buttonCancel.addActionListener(e -> onCancel()); - - // call onCancel() when cross is clicked - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - onCancel(); - } - }); - - // call onCancel() on ESCAPE - contentPane.registerKeyboardAction(e -> - onCancel(), - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - } - - private void onOK() { - // add your code here - dispose(); - } - - Coin showDialog() { - setVisible(true); - if(!cancelled) { - Object idString = coinTable.getValueAt(coinTable.getSelectedRow(), 0); - // If didn't select empty row - if(idString != null) { - int id = Integer.parseInt(idString.toString()); - for (Coin coin : coins) - if (coin.getId() == id) - return coin; - } - } - return null; - } - - private void onCancel() { - // add your code here if necessary - cancelled = true; - dispose(); - } -} diff --git a/src/HelpDialog.form b/src/HelpDialog.form new file mode 100644 index 0000000..4591128 --- /dev/null +++ b/src/HelpDialog.form @@ -0,0 +1,99 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/HelpDialog.java b/src/HelpDialog.java new file mode 100644 index 0000000..94a7513 --- /dev/null +++ b/src/HelpDialog.java @@ -0,0 +1,130 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +// TODO: Translate this file +public class HelpDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JList topicsList; + private JTextArea textArea; + private JScrollPane infoScrollPane; + + private DefaultListModel topics = new DefaultListModel(); + + public HelpDialog() { + setContentPane(contentPane); + setModal(true); + getRootPane().setDefaultButton(buttonOK); + setTitle(Main.getString("helpScreen_title")); + setSize(new Dimension(500,400)); + setMinimumSize(new Dimension(500,400)); + + setIconImage(Main.getIcon().getImage()); + + buttonOK.addActionListener(e -> onOK()); + + topics.addElement(Main.getString("helpScreen_topic_coins")); + topics.addElement(Main.getString("helpScreen_topic_coinFolders")); + topics.addElement(Main.getString("helpScreen_topic_bills")); + topics.addElement(Main.getString("helpScreen_topic_sets")); + topics.addElement(Main.getString("helpScreen_topic_containers")); + topics.addElement(Main.getString("helpScreen_topic_countries")); + topics.addElement(Main.getString("helpScreen_topic_currencies")); + + topicsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + topicsList.setLayoutOrientation(JList.VERTICAL); + topicsList.setListData(topics.toArray()); + + topicsList.addListSelectionListener(e -> { + + // TODO: Use markdown + int index = topicsList.getSelectedIndex(); + + switch (index) { + case 0: { + textArea.setText("Coins are one of the main focus of this program. When entering a new coin required fields include Country, Year, Currency, and Denomination. " + + "Non-required fields can simply be left blank.\n\n" + + "Zero (0) is a legal entry for both year and denomination. This may be used if the item being entered is a token, and therefore doesn't have " + + "a year or a denomination.\n\n" + + "Denomination is the face value of the coin. It can be as small as 0 and can be up to 20 digits long (including decimal places.) " + + "It can also hold fractional numbers up to 3 decimal places. For example, a half cent piece has a denomination of 0.005.\n\n" + + "Coins can be associated with up to 2 images. One image for the front (obverse) of the coin, and one for the back (reverse) of the coin.\n\n" + + "Coins can be placed in sets or in folders. Coins that are in sets or folders do not appears in the list of coins.\n\n" + + "Coins can also be placed inside of a container."); + break; + } + case 1: { + // TODO: Update when folders are finished + textArea.setText("Coin folders are used to hold specific coins. Generally these folders are used to hold a specific type of coin " + + "and collect every date and mint mark for that coin type.\n\n" + + "Folders can have multiple pages, and each page has multiple slots, each of which holds a single coin.\n\n" + + "Coins that are inside of a coin folder will not show in the coins tab of the collection spreadsheet.\n\n" + + "Coin Folders can be placed inside of a container.\n\n" + + "Coin folders are still a work in progress, and will be added at a later date. More information will also be added at the time."); + break; + } + case 2: { + textArea.setText("Banknotes are paper money. When entering a new banknote required fields include Country, Year, Currency, and Denomination. " + + "Non-required fields can simply be left blank.\n\n" + + "Countries and Currencies are pulled from a list in the database, and currencies only appear " + + "as available in countries where they were used, during years while they were used in that country.\n\n" + + "Denomination can be as low as 0, and can be up to 20 digits long (including decimal places.) It can also hold fractional numbers up to 3 decimal places.\n\n" + + "Banknotes can be associated with up to 2 images. One image for the front (obverse), and one for the back (reverse).\n\n" + + "Banknotes can be put inside of a container."); + break; + } + case 3: { + textArea.setText("Sets can hold coins, banknotes, and other sets.\n\n" + + "One reason to have a set inside of a set is if the set has multiple slabs and you want to have separate pictures of each slab.\n\n" + + "Like coins and banknotes, sets can also be associated with up to 2 images. One image for the front (obverse), and one for the back (reverse).\n\n" + + "Sets can also be placed inside of a container."); + break; + } + case 4: { + textArea.setText("Containers are physical items that hold your collection. This can be a box, a display case, a building, a room... whatever " + + "you choose to use as a way to represent where the items in your collection reside.\n\n" + + "The point of containers is to allow you to keep track of not only what is in your collection, but where each piece of your collection is.\n\n" + + "Containers can be placed inside of another container, in a parent/child relationship. The child resides inside of the parent. This is useful " + + "for when you have a box in a box, or a chest of drawers with multiple drawers. The parent can be the chest, and each drawer can be a child.\n\n" + + "Containers can hold a variety of items like coins, banknotes, sets, coin folders, and other containers."); + break; + } + case 5: { + textArea.setText("Countries only have a name, and a list of currencies. When you select a country, the currency dropdown will fill in with " + + "possible currencies for that country. When you enter a year, the currency list will narrow further to the currencies used during that year."); + break; + } + case 6: { + textArea.setText("Currencies have a name, a unique abbreviation for that currency, a symbol (ex: $), an indication of whether the currency should " + + "be placed before or after the numbers, a start date, and an end date.\n\n" + + "The start and end dates are used to select the proper currency when a country and year is selected.\n\n" + + "A currency is also associated with a country, or list of countries."); + break; + } + } + + // Scroll to top of selected area + javax.swing.SwingUtilities.invokeLater(() -> infoScrollPane.getVerticalScrollBar().setValue(0)); + }); + + topicsList.setSelectedIndex(0); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onOK(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onOK(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + dispose(); + } +} diff --git a/src/ImageFilter.kt b/src/ImageFilter.kt index 64b135a..8658aca 100644 --- a/src/ImageFilter.kt +++ b/src/ImageFilter.kt @@ -19,8 +19,8 @@ object ImageExtensions { } class ImageFilter : FileFilter() { - override fun accept(f: File?): Boolean { - if (f!!.isDirectory) { + override fun accept(f: File): Boolean { + if (f.isDirectory) { return true } @@ -36,7 +36,7 @@ class ImageFilter : FileFilter() { for((count, extension) in ImageExtensions.ACCEPTABLE_EXTENSIONS.withIndex()) { if (count > 0) string += ", " - string += ".$extension" + string += "*.$extension" } string += ")" diff --git a/src/ImagePanel.java b/src/ImagePanel.java deleted file mode 100644 index b592cc1..0000000 --- a/src/ImagePanel.java +++ /dev/null @@ -1,41 +0,0 @@ -import javax.swing.*; -import java.awt.*; - -class ImagePanel extends JPanel { - - private static final long serialVersionUID = 1L; - private Image img; - private Image scaled; - - public void setImg(String img) { - this.img = new ImageIcon(img).getImage(); - } - - public void setImg(Image img) { - this.img = img; - } - - @Override - public void invalidate() { - super.invalidate(); - int width = getWidth(); - int height = getHeight(); - - if (width > 0 && height > 0) { - scaled = img.getScaledInstance(getWidth(), getHeight(), - Image.SCALE_SMOOTH); - } - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - g.drawImage(scaled, 0, 0, null); - } - - @Override - public Dimension getPreferredSize() { - return img == null ? new Dimension(200, 200) : new Dimension( - img.getWidth(this), img.getHeight(this)); - } -} diff --git a/src/ItemLocationsWindow.form b/src/ItemLocationsWindow.form index c03963b..d92a97b 100644 --- a/src/ItemLocationsWindow.form +++ b/src/ItemLocationsWindow.form @@ -3,51 +3,32 @@ - + - + - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - + @@ -55,15 +36,15 @@ - + - + - + @@ -71,15 +52,15 @@ - + - + - + @@ -87,15 +68,15 @@ - + - + - + @@ -103,77 +84,183 @@ - + - + - + - + - - + + - - + + + - + - + - + - - - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ItemLocationsWindow.java b/src/ItemLocationsWindow.java index f3d7c42..75a04ae 100644 --- a/src/ItemLocationsWindow.java +++ b/src/ItemLocationsWindow.java @@ -1,11 +1,10 @@ -import items.DatabaseConnection; - import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.*; import java.io.File; +import java.sql.SQLException; import java.util.prefs.Preferences; public class ItemLocationsWindow extends JDialog { @@ -23,6 +22,11 @@ public class ItemLocationsWindow extends JDialog { private JButton setDefaultUsernameButton; private JButton setDefaultPasswordButton; private JButton setDefaultLocationButton; + private JButton setDefaultPortButton; + private JSpinner portNumberInput; + private JSpinner timeoutInput; + private JButton setDefaultTimeoutButton; + private JButton timeoutHelpButton; private final JFrame parent; @@ -32,18 +36,36 @@ public ItemLocationsWindow(JFrame parent) { super(parent); setContentPane(contentPane); setModal(true); - setMinimumSize(new Dimension(150,150)); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); setResizable(false); getRootPane().setDefaultButton(OKButton); + setIconImage(Main.getIcon().getImage()); this.parent = parent; + timeoutHelpButton.setBorder(null); + - setTitle("Item Locations"); + setTitle(Main.getString("itemLoc_title")); databaseServerInput.setText(Main.getSettingDatabaseServer()); databaseNameInput.setText(Main.getSettingDatabaseName()); databaseUsernameInput.setText(Main.getSettingDatabaseUsername()); imagesInput.setText(Main.getSettingImagePath()); + SpinnerModel portModel = + new SpinnerNumberModel(Main.getSettingPortNumber(), //initial value + 0, //min + 65535, //max + 1); //step + portNumberInput.setModel(portModel); + // Remove comma (,) from numbers (ex: 3306 instead of 3,306) + JSpinner.NumberEditor editor = new JSpinner.NumberEditor(portNumberInput, "#"); + portNumberInput.setEditor(editor); + SpinnerModel timeoutModel = + new SpinnerNumberModel(Main.getSettingDbTimeout(), //initial value + 0, //min + Integer.MAX_VALUE, //max + 1); //step + timeoutInput.setModel(timeoutModel); passwordInput.setText("12345678"); passwordInput.getDocument().addDocumentListener(new DocumentListener() { @@ -79,6 +101,17 @@ public void windowClosing(WindowEvent e) { setDefaultUsernameButton.addActionListener( e -> databaseUsernameInput.setText(Main.DEFAULT_DATABASE_USERNAME)); setDefaultPasswordButton.addActionListener( e -> passwordInput.setText(Main.DEFAULT_DATABASE_PASSWORD)); setDefaultLocationButton.addActionListener( e -> imagesInput.setText(Main.getDefaultImagesLocation())); + setDefaultPortButton.addActionListener( e -> portNumberInput.setValue(Main.DEFAULT_PORT_NUMBER)); + setDefaultTimeoutButton.addActionListener( e -> timeoutInput.setValue(Main.DEFAULT_TIMEOUT_SECONDS)); + + // Change initial focus from Default server button to server address entry + addWindowListener(new WindowAdapter() { + @Override + public void windowOpened(WindowEvent e) { + super.windowOpened(e); + databaseServerInput.requestFocusInWindow(); + } + }); } private void onOK() { @@ -94,27 +127,6 @@ private void onOK() { else password = Main.getSettingDatabasePassword(); - DatabaseConnection testConnection = null; - - // Test connection - try { - - testConnection = new DatabaseConnection( - databaseUsernameInput.getText(), - password, - databaseNameInput.getText(), - databaseServerInput.getText() - ); - } - // If connection failed - catch (Exception e) { - int result = JOptionPane.showConfirmDialog(parent, "Database connection failed. Continue anyway?", - "Connection Failed", JOptionPane.YES_NO_OPTION); - - if(result != JOptionPane.YES_OPTION) - error = true; - } - File directory = new File(imagesInput.getText()); // Check to make sure file directory exists @@ -123,8 +135,10 @@ private void onOK() { } else { // If directory doesn't exist, ask if we should create it - int result = JOptionPane.showConfirmDialog(parent, "Images directory doesn't exist. Create it now?", - "Create Directory", JOptionPane.YES_NO_OPTION); + int result = JOptionPane.showConfirmDialog(parent, + Main.getString("itemLoc_error_message_imagesDirectoryMissing"), + Main.getString("itemLoc_error_title_imagesDirectoryMissing"), + JOptionPane.YES_NO_OPTION); // If we should create it if(result == JOptionPane.YES_OPTION) { @@ -133,8 +147,10 @@ private void onOK() { if(!created) { - JOptionPane.showMessageDialog(parent, "Failed to create directory!", - "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(parent, + Main.getString("itemLoc_error_message_directoryCreateFail"), + Main.getString("itemLoc_error_title_directoryCreateFail"), + JOptionPane.ERROR_MESSAGE); error = true; } @@ -146,15 +162,66 @@ private void onOK() { // If everything is OK, save everything and close the window if(!error) { - prefs.put(Main.SETTING_DATABASE_NAME, databaseNameInput.getText()); - prefs.put(Main.SETTING_DATABASE_SERVER, databaseServerInput.getText()); - prefs.put(Main.SETTING_DATABASE_USERNAME, databaseUsernameInput.getText()); - prefs.put(Main.SETTING_IMAGE_PATH, imagesInput.getText()); - prefs.put(Main.SETTING_DATABASE_PASSWORD, password); + ItemLocationsWindow window = this; + + ((Main) parent).api.setConnectionTimeout(Integer.parseInt(timeoutInput.getValue().toString())); + ((Main) parent).api.setImagePath(imagesInput.getText()); + + SwingWorker worker = new SwingWorker<>() { + + boolean sqlE = false; + boolean cnf = false; + + @Override + public Void doInBackground() { + try { + ((Main) parent).api.setDbInfo(databaseServerInput.getText(), + databaseNameInput.getText(), + portNumberInput.getValue().toString(), + databaseUsernameInput.getText(), + password); + } + catch (SQLException sqlException) { + sqlE = true; + } + catch (ClassNotFoundException cnfE) { + cnf = true; + } + + return null; + } - ((Main)parent).databaseConnection = testConnection; + @Override + public void done() { + if(sqlE) { + + JOptionPane.showMessageDialog(window, + Main.getString("itemLoc_error_message_databaseConnFail"), + Main.getString("itemLoc_error_title_databaseConnFail"), + JOptionPane.ERROR_MESSAGE); + } + else if(cnf) { + JOptionPane.showMessageDialog(window, + Main.getString("itemLoc_error_message_driverMissing"), + Main.getString("itemLoc_error_title_driverMissing"), + JOptionPane.ERROR_MESSAGE); + } + else { + Main.setSettingDatabaseName(databaseNameInput.getText()); + Main.setSettingDatabaseServer(databaseServerInput.getText()); + Main.setSettingDatabaseUsername(databaseUsernameInput.getText()); + Main.setSettingImagePath(imagesInput.getText()); + Main.setSettingDatabasePassword(password); + Main.setSettingPortNumber((int) portNumberInput.getValue()); + + Main.setSettingDbTimeout((int)timeoutInput.getValue()); + + dispose(); + } + } + }; - dispose(); + Main.showBackgroundPopup(contentPane, Main.getString("databaseQueryWindow_message"), Main.getString("databaseQueryWindow_title"), worker); } } @@ -163,6 +230,10 @@ void openFileChooser() { fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + // Default to directory in text box + if(imagesInput.getText() != null && !imagesInput.getText().equals("")) + fc.setCurrentDirectory(new File(imagesInput.getText())); + int returnVal = fc.showOpenDialog(parent); if (returnVal == JFileChooser.APPROVE_OPTION) { diff --git a/src/LookAndFeelWindow.form b/src/LookAndFeelWindow.form new file mode 100644 index 0000000..9b31419 --- /dev/null +++ b/src/LookAndFeelWindow.form @@ -0,0 +1,67 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/LookAndFeelWindow.java b/src/LookAndFeelWindow.java new file mode 100644 index 0000000..419fe72 --- /dev/null +++ b/src/LookAndFeelWindow.java @@ -0,0 +1,87 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.prefs.Preferences; + +public class LookAndFeelWindow extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JComboBox themeComboBox; + + private final JFrame parent; + + LookAndFeel startingLook; + UIManager.LookAndFeelInfo[] looks; + + public LookAndFeelWindow(JFrame parent) { + super((parent)); + setContentPane(contentPane); + setModal(true); + this.parent = parent; + setMinimumSize(new Dimension(250,100)); + getRootPane().setDefaultButton(buttonOK); + startingLook = UIManager.getLookAndFeel(); + + setTitle("Look and Feel"); + + // Find available looks + looks = UIManager.getInstalledLookAndFeels(); + for (UIManager.LookAndFeelInfo look : looks) { + themeComboBox.addItem(look.getName()); + } + + themeComboBox.setSelectedItem(startingLook.getName()); + + Window window = this; + + themeComboBox.addActionListener(e -> { + String className = looks[themeComboBox.getSelectedIndex()].getClassName(); + try { + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(window); + window.pack(); + SwingUtilities.updateComponentTreeUI(parent); + parent.pack(); + parent.invalidate(); + + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { + ex.printStackTrace(); + } + }); + + buttonOK.addActionListener(e -> onOK()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onOK(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onOK(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + + String className = looks[themeComboBox.getSelectedIndex()].getClassName(); + + if(startingLook.equals(themeComboBox.getSelectedItem())) { + try { + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(parent)); + parent.pack(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(Main.SETTING_LOOK_AND_FEEL, className); + + dispose(); + } +} diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..3294b0a --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Main-Class: Main +Implementation-Title: Numismatist Helper +Implementation-Version: 0.4 diff --git a/src/Main.form b/src/Main.form index 1ac757d..b889e86 100644 --- a/src/Main.form +++ b/src/Main.form @@ -1,9 +1,9 @@
- + - + @@ -11,66 +11,108 @@ - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + - - + + - - - - - - - - - - - + - + - + + + + + + + + + + @@ -82,107 +124,148 @@ - + - - + + - - + + - - + + - + - - + + - - + + + - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - - - - - - - - - - - - - - - - + - + - - - - - - - - + - + - + - - - - - - - - - + + @@ -192,24 +275,35 @@ - + - - - - - - + - + + + + + + + + + + + + + + + + + diff --git a/src/Main.java b/src/Main.java index f8aa784..4a9b386 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,54 +1,209 @@ -import items.DatabaseConnection; import javax.swing.*; +import javax.swing.filechooser.FileSystemView; import java.awt.*; import java.awt.event.*; import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.sql.SQLException; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.prefs.Preferences; +@SuppressWarnings("unused") public class Main extends JFrame { - static String app_name = "Coin Collection"; - static String version = "1.0"; - - static final String DEFAULT_DATABASE_SERVER = "localhost"; - static final String DEFAULT_DATABASE_NAME = "CoinCollection"; - static final String DEFAULT_DATABASE_USERNAME = "coins"; - static final String DEFAULT_DATABASE_PASSWORD = "coinDatabasePassword"; + static final String DEFAULT_DATABASE_SERVER = DatabaseConnection.DEFAULT_DATABASE_SERVER; + static final String DEFAULT_DATABASE_NAME = DatabaseConnection.DEFAULT_DATABASE_NAME; + static final String DEFAULT_DATABASE_USERNAME = DatabaseConnection.DEFAULT_DATABASE_USERNAME; + static final String DEFAULT_DATABASE_PASSWORD = DatabaseConnection.DEFAULT_DATABASE_PASSWORD; + static final int DEFAULT_PORT_NUMBER = DatabaseConnection.DEFAULT_PORT_NUMBER; + static final int DEFAULT_TIMEOUT_SECONDS = DatabaseConnection.DEFAULT_TIMEOUT_SECONDS; static final String SETTING_IMAGE_PATH = "imagesFolder"; static final String SETTING_DATABASE_SERVER = "databaseServer"; static final String SETTING_DATABASE_NAME = "databaseName"; static final String SETTING_DATABASE_USERNAME = "databaseUsername"; static final String SETTING_DATABASE_PASSWORD = "password"; + static final String SETTING_PORT_NUMBER = "portNumber"; + static final String SETTING_DB_TIMEOUT = "timeout"; + + static final String SETTING_CUSTOM_COUNTRIES = "customCountryList"; + + static final String SETTING_LOOK_AND_FEEL = "lookAndFeel"; + static final String SETTING_LAST_DIRECTORY = "lastDirectoryLocation"; + + static final Color COLOR_SUCCESS = new Color(0, 100, 0); + static final Color COLOR_WARNING = new Color(255, 100, 0); + static final Color COLOR_ERROR = new Color(200, 0, 0); private JPanel contentPane; private JButton addCoinButton; private JButton addSetButton; private JButton addBillButton; - private JButton viewCollectionTreeButton; + private JButton addBookFolderButton; private JButton viewSpreadsheetButton; - private JButton viewTotalValuesButton; + private JLabel titleLabel; + private JButton addContainerButton; + // TODO: Make these work + private JButton addCountryButton; + private JButton addCurrencyButton; - DatabaseConnection databaseConnection; + NumismatistAPI api = new NumismatistAPI(); ResizeListener resizeListener = null; + private boolean firstOpen = true; + + public String getVersion() { + try { + Enumeration resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"); + while (resources.hasMoreElements()) { + + Manifest manifest = new Manifest(resources.nextElement().openStream()); + String title = manifest.getMainAttributes().getValue("Implementation-Title"); + + // check that this is the proper manifest + if(title != null && title.equals("Numismatist Helper")) { + return manifest.getMainAttributes().getValue("Implementation-Version"); + } + } + } + catch (IOException E) { + // ignore + } + + return ""; + } + + public static ImageIcon getIcon() { + ImageIcon icon = null; + try { + // Get icon from file + icon = new ImageIcon("icon.png"); + // If file doesn't exist, try to get from resources + if (icon.getIconHeight() == -1) + icon = new ImageIcon(Objects.requireNonNull(Main.class.getResource("/Images/icon.png"))); + } + catch(Exception e){ + e.printStackTrace(); + } + + return icon; + } + + public static void showBackgroundPopup(Component parent, String message, String title, SwingWorker worker) { + + final JOptionPane optionPane = new JOptionPane(message, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null); + + JDialog dialog = new JDialog(); + dialog.setTitle(title); + + // Set window icon to app icon + dialog.setIconImage(getIcon().getImage()); + dialog.setContentPane(optionPane); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + dialog.pack(); + // Center in the window + dialog.setLocationRelativeTo(parent); + + // Cancel the worker if dialog is close with X + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + super.windowClosed(e); + + worker.cancel(true); + } + }); + + // Show the dialog when the work starts, and close it when the work ends + worker.addPropertyChangeListener(evt -> { + if(evt.getPropertyName().equals("state") && + evt.getNewValue().toString().equals("STARTED")) { + dialog.setModal(false); + dialog.setVisible(true); + parent.setEnabled(false); + } + if(evt.getPropertyName().equals("state") && + evt.getNewValue().toString().equals("DONE")) { + parent.setEnabled(true); + dialog.dispose(); + } + }); + try { + worker.execute(); + } + catch (Exception e) { + dialog.dispose(); + throw e; + } + } + + public static ArrayList getResourcesList(String directoryName) throws URISyntaxException, IOException { + ArrayList filenames = new ArrayList<>(); + + directoryName = "res/" + directoryName; + URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName); + if (url != null) { + if (url.getProtocol().equals("file")) { + File file = Paths.get(url.toURI()).toFile(); + File[] files = file.listFiles(); + if (files != null) { + for (File filename : files) { + filenames.add(filename.getName()); + } + } + } else if (url.getProtocol().equals("jar")) { + String dirname = directoryName + "/"; + String path = url.getPath(); + String jarPath = path.substring(5, path.indexOf("!")); + try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith(dirname) && !dirname.equals(name)) { + URL resource = Thread.currentThread().getContextClassLoader().getResource(name); + if(resource != null) { + String resString = resource.toString().substring(resource.toString().lastIndexOf("/") + 1); + filenames.add(resString); + } + } + } + } + } + } + return filenames; + } + public interface ResizeListener{ void onResize(int width, int height); } - public static boolean copyFile(File scr, String dest) { - return copyFile(scr, new File(dest)); + /* Requires JRE 7, Java 1.7 + public static void copyFile(String from, String to) throws IOException{ + Path src = Paths.get(from); + Path dest = Paths.get(to); + Files.copy(src.toFile(), dest.toFile()); + }*/ + + public static boolean copyFile(File src, String dest) { + return copyFile(src, new File(dest)); } - public static boolean copyFile(String scr, File dest) { - return copyFile(new File(scr), dest); + public static boolean copyFile(String src, File dest) { + return copyFile(new File(src), dest); } - public static boolean copyFile(String scr, String dest) { - return copyFile(new File(scr), new File(dest)); + public static boolean copyFile(String src, String dest) { + return copyFile(new File(src), new File(dest)); } public static boolean copyFile(File src, File dest) { @@ -83,6 +238,12 @@ public static boolean copyFile(File src, File dest) { return true; } + /** + * Takes a String and adds escape characters for anything that needs them + * + * @param value The original String that needs to be modified + * @return The modified String + */ public static String escapeForJava(String value) { StringBuilder builder = new StringBuilder(); @@ -107,18 +268,73 @@ else if( c < 32 || c >= 127 ) return builder.toString(); } - /* Requires JRE 7, Java 1.7 - public static void copyFile(String from, String to) throws IOException{ - Path src = Paths.get(from); - Path dest = Paths.get(to); - Files.copy(src.toFile(), dest.toFile()); - }*/ + public static void setSettingCustomCountries(ArrayList customCountries) { + String csv = String.join(",", customCountries); + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(SETTING_CUSTOM_COUNTRIES, csv); + } + public ArrayList getSettingCustomCountries() { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + + String csv = prefs.get(SETTING_CUSTOM_COUNTRIES, ""); + + return new ArrayList<>(Arrays.asList(csv.split(","))); + } + + public static void setSettingDatabasePassword(String password) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(Main.SETTING_DATABASE_PASSWORD, password); + } public static String getSettingDatabasePassword() { Preferences prefs = Preferences.userNodeForPackage(Main.class); return prefs.get(SETTING_DATABASE_PASSWORD, DEFAULT_DATABASE_PASSWORD); } + public static int getSettingPortNumber() { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + return prefs.getInt(SETTING_PORT_NUMBER, DEFAULT_PORT_NUMBER); + } + + public static void setSettingPortNumber(int port) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.putInt(SETTING_PORT_NUMBER, port); + } + + public static void setSettingPortNumber(String port) { + setSettingPortNumber(Integer.parseInt(port)); + } + + public static int getSettingDbTimeout() { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + return prefs.getInt(SETTING_DB_TIMEOUT, DEFAULT_TIMEOUT_SECONDS); + } + + public static void setSettingDbTimeout(int timeout) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.putInt(SETTING_DB_TIMEOUT, timeout); + } + + public static String getSettingLookAndFeel() { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + return prefs.get(SETTING_LOOK_AND_FEEL, UIManager.getSystemLookAndFeelClassName()); + } + + public static String getSettingLastDirectory() { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + return prefs.get(SETTING_LAST_DIRECTORY, FileSystemView.getFileSystemView().getDefaultDirectory().getPath()); + } + + public static void setSettingLastDirectory(String path) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(SETTING_LAST_DIRECTORY, path); + } + + public static void setSettingImagePath(String path) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(SETTING_IMAGE_PATH, path); + } + public static String getSettingImagePath() { Preferences prefs = Preferences.userNodeForPackage(Main.class); @@ -131,23 +347,37 @@ public static String getDefaultImagesLocation() { String imageDirectory = System.getProperty("user.home"); if(os.toLowerCase().contains("windows")) - imageDirectory += "\\My Pictures\\Coin Collection"; + imageDirectory += "\\My Pictures\\" + Main.getString("app_name"); else if(os.toLowerCase().contains("linux")) - imageDirectory += "/Pictures/CoinCollection"; + imageDirectory += "/Pictures/" + Main.getString("app_name"); return imageDirectory; } + public static void setSettingDatabaseServer(String server) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(Main.SETTING_DATABASE_SERVER, server); + } + public static String getSettingDatabaseServer() { Preferences prefs = Preferences.userNodeForPackage(Main.class); return prefs.get(SETTING_DATABASE_SERVER, DEFAULT_DATABASE_SERVER); } + public static void setSettingDatabaseName(String name) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(Main.SETTING_DATABASE_NAME, name); + } + public static String getSettingDatabaseName() { Preferences prefs = Preferences.userNodeForPackage(Main.class); return prefs.get(SETTING_DATABASE_NAME, DEFAULT_DATABASE_NAME); } + public static void setSettingDatabaseUsername(String username) { + Preferences prefs = Preferences.userNodeForPackage(Main.class); + prefs.put(Main.SETTING_DATABASE_USERNAME, username); + } public static String getSettingDatabaseUsername() { Preferences prefs = Preferences.userNodeForPackage(Main.class); return prefs.get(SETTING_DATABASE_USERNAME, DEFAULT_DATABASE_USERNAME); @@ -156,39 +386,58 @@ public static String getSettingDatabaseUsername() { public static void main(String[] args) { Main dialog = new Main(); dialog.pack(); + + // Center window in screen + dialog.setLocationRelativeTo(null); dialog.setVisible(true); } public Main() { setContentPane(contentPane); + Locale.setDefault(new Locale("en", "US")); + + // set theme try { - // Get icon from file - ImageIcon icon = new ImageIcon("icon.png"); - // If file doesn't exist, try to get from resources - if (icon.getIconHeight() == -1) - icon = new ImageIcon(getClass().getResource("/Images/icon.png")); - setIconImage(icon.getImage()); + UIManager.setLookAndFeel(getSettingLookAndFeel()); + SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(contentPane)); } - catch(Exception e){ - e.printStackTrace(); + catch (Exception e) { + //ignore } + ImageIcon icon = getIcon(); + if(icon != null && icon.getImage() != null) + setIconImage(icon.getImage()); + setMinimumSize(new Dimension(800,600)); - setTitle(app_name); + setTitle(getAppName()); + titleLabel.setText(getAppName()); + + // Make title bold and larger + Font titleFont = titleLabel.getFont(); + titleLabel.setFont(new Font(titleFont.getName(), Font.BOLD, 36)); + + api.setTopOfCountriesList(getSettingCustomCountries()); addMenu(); addCoinButton.addActionListener(e -> showNewCoinWindow()); - addBillButton.addActionListener(e -> showNewBillWindow() ); - addSetButton.addActionListener( e -> showNewSetWindow()); + addBookFolderButton.addActionListener(e -> showNewBookWindow()); + + addContainerButton.addActionListener(e -> { + NewContainerDialog newContainerDialog = new NewContainerDialog(this); + newContainerDialog.pack(); + newContainerDialog.setLocationRelativeTo(this); + newContainerDialog.setVisible(true); + }); viewSpreadsheetButton.addActionListener( e-> { CollectionTableScreen collectionTableScreen = new CollectionTableScreen(this); - changeScreen(collectionTableScreen.getPanel(), "Collection"); + changeScreen(collectionTableScreen.getPanel(), getString("viewColl_title")); }); // call onCancel() when cross is clicked @@ -198,97 +447,187 @@ public void windowClosing(WindowEvent e) { onCancel(); } }); - - // call onCancel() on ESCAPE - contentPane.registerKeyboardAction(e -> onCancel(), - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - // Hide these buttons until they are ready - viewCollectionTreeButton.setVisible(false); - viewTotalValuesButton.setVisible(false); - - // Close the database connection when program closes - Runtime.getRuntime().addShutdownHook(new Thread(() -> - { - try { - databaseConnection.getStatement().close(); - } - catch (Exception e){ - e.printStackTrace(); - } - }, "Shutdown-thread")); } private void addMenu() { JMenuBar menuBar = new JMenuBar(); - JMenu fileMenu = new JMenu("File"); - JMenu settingsMenu = new JMenu("Settings"); - JMenu helpMenu = new JMenu("Help"); + JMenu fileMenu = new JMenu(getString("menu_file")); + JMenu collectionMenu = new JMenu(getString("menu_collection")); + JMenu settingsMenu = new JMenu(getString("menu_settings")); + JMenu viewMenu = new JMenu(getString("menu_view")); + JMenu helpMenu = new JMenu(getString("menu_help")); setJMenuBar(menuBar); // Setup file menu - JMenuItem newCoin = new JMenuItem("New Coin"); - newCoin.addActionListener( e-> showNewCoinWindow()); - fileMenu.add(newCoin); - JMenuItem newSet = new JMenuItem("New Set"); - newSet.addActionListener( e -> showNewSetWindow()); - fileMenu.add(newSet); + JMenuItem home = new JMenuItem(getString("fileMenu_home")); + home.addActionListener( e -> changeScreen(getPanel(), "")); + fileMenu.add(home); fileMenu.addSeparator(); - JMenuItem newBill = new JMenuItem("New Bill"); - newBill.addActionListener( e -> showNewBillWindow()); - fileMenu.add(newBill); - fileMenu.addSeparator(); - JMenuItem exit = new JMenuItem("Exit"); + JMenuItem exit = new JMenuItem(getString("fileMenu_exit")); exit.addActionListener( e -> onCancel()); fileMenu.add(exit); - JMenuItem itemLocations = new JMenuItem(("Item Locations")); + // Setup collection menu + JMenuItem newCoin = new JMenuItem(getString("collMenu_addCoin")); + newCoin.addActionListener( e-> showNewCoinWindow()); + collectionMenu.add(newCoin); + + JMenuItem newBook = new JMenuItem(getString("collMenu_addCoinFolder")); + newBook.addActionListener(e -> showNewBookWindow()); + //collectionMenu.add(newBook); + + JMenuItem newSet = new JMenuItem(getString("collMenu_addCoinSet")); + newSet.addActionListener( e -> showNewSetWindow()); + collectionMenu.add(newSet); + + JMenuItem newBill = new JMenuItem(getString("collMenu_addBill")); + newBill.addActionListener( e -> showNewBillWindow()); + collectionMenu.add(newBill); + collectionMenu.addSeparator(); + + JMenuItem newContainer = new JMenuItem(getString("collMenu_addContainer")); + newContainer.addActionListener(e -> { + NewContainerDialog newContainerDialog = new NewContainerDialog(this); + newContainerDialog.pack(); + newContainerDialog.setLocationRelativeTo(this); + newContainerDialog.setVisible(true); + }); + collectionMenu.add(newContainer); + collectionMenu.addSeparator(); + + JMenuItem viewCollection = new JMenuItem(getString("collMenu_viewColl")); + viewCollection.addActionListener( e -> { + CollectionTableScreen collectionTableScreen = new CollectionTableScreen(this); + changeScreen(collectionTableScreen.getPanel(), getString("viewColl_title")); + }); + collectionMenu.add(viewCollection); + + JMenuItem itemLocations = new JMenuItem((getString("setMenu_itemLoc"))); itemLocations.addActionListener( e-> { ItemLocationsWindow itemLocationsWindow = new ItemLocationsWindow(this); itemLocationsWindow.pack(); + // Center window in this window + itemLocationsWindow.setLocationRelativeTo(this); itemLocationsWindow.setVisible(true); + + getPanel().invalidate(); + }); + + JMenuItem customize = new JMenuItem((getString("setMenu_custom"))); + customize.addActionListener( e -> { + DoubleListDialog customizeDialog = new DoubleListDialog(api.getCountries(), api.getTopOfCountriesList()); + customizeDialog.setSourceMethod("getName"); + customizeDialog.setSourceName(Main.getString("customizeCountries_label_available")); + customizeDialog.setDestName(Main.getString("customizeCountries_label_keepOnTop")); + customizeDialog.setTitle(Main.getString("customizeCountries_title")); + customizeDialog.setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + + customizeDialog.pack(); + customizeDialog.setLocationRelativeTo(this); + customizeDialog.setVisible(true); + + // Runs when dialog closes + if(!customizeDialog.cancelled) { + // Get items in destination + ArrayList objects = new ArrayList<>(Arrays.asList(customizeDialog.getDest())); + // Remove randomly added empty String item + objects.remove(""); + + // Convert Objects to Strings + ArrayList strings = new ArrayList<>(objects.size()); + for (Object item : objects) { + strings.add(item.toString()); + } + + // Set new list and save it to settings + api.setTopOfCountriesList(strings); + setSettingCustomCountries(api.getTopOfCountriesList()); + } }); settingsMenu.add(itemLocations); + settingsMenu.add(customize); + + JMenuItem lookAndFeel = new JMenuItem((getString("viewMenu_look"))); + lookAndFeel.addActionListener( e-> { + LookAndFeelWindow lookAndFeelWindow = new LookAndFeelWindow(this); + lookAndFeelWindow.pack(); + // Center window in this window + lookAndFeelWindow.setLocationRelativeTo(this); + lookAndFeelWindow.setVisible(true); + }); + viewMenu.add(lookAndFeel); + + JMenuItem help = new JMenuItem(getString("helpMenu_help")); + help.addActionListener( e-> { + HelpDialog helpDialog = new HelpDialog(); + helpDialog.pack(); + // Center in this window + helpDialog.setLocationRelativeTo(this); + helpDialog.setVisible(true); + }); - JMenuItem about = new JMenuItem("About"); + JMenuItem about = new JMenuItem(getString("helpMenu_about")); about.addActionListener( e-> { AboutScreen aboutScreen = new AboutScreen(this); aboutScreen.pack(); + // Center in this window + aboutScreen.setLocationRelativeTo(this); aboutScreen.setVisible(true); }); + helpMenu.add(help); helpMenu.add(about); menuBar.add(fileMenu); + menuBar.add(collectionMenu); menuBar.add(settingsMenu); + menuBar.add(viewMenu); menuBar.add(helpMenu); } private void showNewCoinWindow() { AddCoinScreen newScreen = new AddCoinScreen(this); - changeScreen(newScreen.getPanel(), "Add Coin"); + changeScreen(newScreen.getPanel(), getString("addCoin_title_add")); + } + + private void showNewBookWindow() { + + NewFolderDialog newFolderDialog = new NewFolderDialog(this); + newFolderDialog.pack(); + newFolderDialog.setLocationRelativeTo(this); + newFolderDialog.setVisible(true); } private void showNewBillWindow() { AddBillScreen newScreen = new AddBillScreen(this); - changeScreen(newScreen.getPanel(), "Add Bill"); + changeScreen(newScreen.getPanel(), Main.getString("addBill_title_add")); } private void showNewSetWindow() { AddSetScreen newScreen = new AddSetScreen(this); - changeScreen(newScreen.getPanel(), "Add Set"); + changeScreen(newScreen.getPanel(), Main.getString("addSet_title_add")); } private void onCancel() { dispose(); + + // Cancels any background tasks and closes child windows + System.exit(0); } public static String getAppName() { - return app_name; + return getString("app_name", Locale.getDefault()); + } + + public static String getString(String stringName) { + return getString(stringName, Locale.getDefault()); + } + + public static String getString(String stringName, Locale locale) { + return ResourceBundle.getBundle("res.strings", locale).getString(stringName); } public void changeScreen(JPanel newPanel, String title) { @@ -312,31 +651,62 @@ public JPanel getPanel() { public void setVisible(boolean b) { super.setVisible(b); - // Setup database connection - try { - databaseConnection = new DatabaseConnection(Main.getSettingDatabaseUsername(), - Main.getSettingDatabasePassword(), - Main.getSettingDatabaseName(), - Main.getSettingDatabaseServer()); - } - catch (ClassNotFoundException ex) { - JOptionPane.showMessageDialog(this, - "SQL driver not found. Please load mysql-connector-java-*.jar into your project - " + - "where * is the latest version.", - "SQL Driver Missing", - JOptionPane.ERROR_MESSAGE); - } - // Show settings window if connection was unsuccessful - catch (SQLException ex) { - - JOptionPane.showMessageDialog(this, - "Error connecting to database. Please check your settings.", - " Connection Error", - JOptionPane.ERROR_MESSAGE); - - ItemLocationsWindow itemLocationsWindow = new ItemLocationsWindow(this); - itemLocationsWindow.pack(); - itemLocationsWindow.setVisible(true); + Main window = this; + + api.setImagePath(getSettingImagePath()); + + // Connect to database in background + if(firstOpen) { + SwingWorker worker = new SwingWorker<>() { + + boolean sqlE = false; + boolean cnf = false; + + @Override + public Void doInBackground() { + + try { + window.api.setDbInfo(getSettingDatabaseServer(), + getSettingDatabaseName(), + getSettingPortNumber(), + getSettingDatabaseUsername(), + getSettingDatabasePassword()); + } catch (SQLException sqlException) { + sqlE = true; + } catch (ClassNotFoundException cnfE) { + cnf = true; + } + + return null; + } + + @Override + public void done() { + if (sqlE) { + JOptionPane.showMessageDialog(window, + Main.getString("itemLoc_error_message_databaseConnFail"), + Main.getString("itemLoc_error_title_databaseConnFail"), + JOptionPane.ERROR_MESSAGE); + + ItemLocationsWindow itemLocationsWindow = new ItemLocationsWindow(window); + itemLocationsWindow.pack(); + itemLocationsWindow.setLocationRelativeTo(window); + itemLocationsWindow.setVisible(true); + } else if (cnf) { + JOptionPane.showMessageDialog(window, + Main.getString("itemLoc_error_message_driverMissing"), + Main.getString("itemLoc_error_title_driverMissing"), + JOptionPane.ERROR_MESSAGE); + } + } + }; + + Main.showBackgroundPopup(this, + Main.getString("databaseQueryWindow_message"), + Main.getString("databaseQueryWindow_title"), + worker); + + firstOpen = false; } } diff --git a/src/MyDocFilter.kt b/src/MyDocFilter.kt new file mode 100644 index 0000000..8a5790b --- /dev/null +++ b/src/MyDocFilter.kt @@ -0,0 +1,288 @@ +import java.util.* +import javax.swing.text.AttributeSet +import javax.swing.text.DocumentFilter + +/** + * A document filter for text fields, which will only accept certain formats of input. + * Input will be rejected regardless of how it got there... whether it is typed, pasted, or inserted some other way. + * + * This class, as it stands, does not filter anything. You must use a subclass in order to filter input. + * + * When creating a custom filter, simply create a subclass of MyDocFilter and override the test function. + * This function determines whether or not input is acceptable for the filter. If this method returns true, + * the input will be allowed, if it returns false the input will not be entered. + * + * Example of setting filter on JTextField in Java: + * ((PlainDocument) textField.getDocument()).setDocumentFilter(new MyIntFilter()); + * in Kotlin: + * (textField.document as PlainDocument).setDocumentFilter(MyIntFilter()) + */ +open class MyDocFilter : DocumentFilter() { + + companion object { + /** + * Create a filter that only accepts years as values, with the current year as max accepted value. + * Negative year is BC, or BCE. Positive is AD, or CE + * 0 is invalid, but is accepted here in case you want to use 0 as something like "No year provided" + */ + fun getCurrentYearFilter() : MyIntFilter { + val yearFilter = MyIntFilter() + // Current year is the max value - If you need to accept future years, use a different filter + yearFilter.setMaxValue(Calendar.getInstance()[Calendar.YEAR]) + + return yearFilter + } + + /** + * Create a filter that accepts denominations for money (coins and banknotes) + */ + fun getDenominationFilter() : MyDoubleFilter { + val denominationFilter = MyDoubleFilter() + // Denomination is always more than 0 + denominationFilter.minValue = 0.0 + // Allow 3 decimal points of precision for half cents + denominationFilter.maxPrecision = 3 + + return denominationFilter + } + + /** + * Creates a filter that accepts the value of something + */ + fun getValueFilter() : MyDoubleFilter { + val valueFilter = MyDoubleFilter() + // value is always more than 0 + valueFilter.minValue = 0.0 + valueFilter.maxPrecision = 2 + + return valueFilter + } + } + + // Called when text is inserted + override fun insertString(fb: FilterBypass, offset: Int, string: String, + attr: AttributeSet) { + val doc = fb.document + val sb = StringBuilder() + sb.append(doc.getText(0, doc.length)) + sb.insert(offset, string) + + if (test(sb.toString())) { + super.insertString(fb, offset, string, attr) + } else { + // warn the user and don't allow the insert + } + } + + /** + * Override this function, and only this function, when creating a custom filter. + * It should return true when the input is valid, otherwise false + */ + protected open fun test(text: String): Boolean { + return true + } + + // Called when text is replaced + override fun replace(fb: FilterBypass, offset: Int, length: Int, text: String, + attrs: AttributeSet?) { + val doc = fb.document + val sb = StringBuilder() + sb.append(doc.getText(0, doc.length)) + sb.replace(offset, offset + length, text) + + if (test(sb.toString())) { + super.replace(fb, offset, length, text, attrs) + } else { + // warn the user and don't allow the insert + } + } + + // Called when text is removed + override fun remove(fb: FilterBypass, offset: Int, length: Int) { + val doc = fb.document + val sb = StringBuilder() + sb.append(doc.getText(0, doc.length)) + sb.delete(offset, offset + length) + + // If input is deleted entirely, or input is valid + if (sb.toString().isEmpty() || test(sb.toString())) { + super.remove(fb, offset, length) + } else { + // warn the user and don't allow the insert + } + } +} + +/** + * A filter that only accepts letters + */ +class MyLetterFilter : MyDocFilter() { + + var maxLetters = Int.MAX_VALUE + + override fun test(text: String): Boolean { + + // allow empty input + if(text == "") + return true + + // Limit to certain # of characters + if(text.length > maxLetters) + return false + + // If anything is not a letter, return false + for(char in text) { + if(!Character.isLetter(char)) + return false + } + + // If it got here, we're OK + return true + } +} + +/** + * A filter that only accepts integers + */ +class MyIntFilter : MyDocFilter() { + + // Limit input to a specific range + private var minValue = Int.MIN_VALUE + private var maxValue = Int.MAX_VALUE + + // Limit input to a specific number of digits + var maxDigits = Int.MAX_VALUE + + override fun test(text: String): Boolean { + try { + + // Accept empty input + if(text == "") + return true + + // Accept negative sign + if (text == "-" && minValue < 0) + return true + + // don't count - as a digit, if it's there + val digits = if(text[0] == '-') + text.length - 1 + else + text.length + + // Limit to certain # of digits + if(digits > maxDigits) + return false + + // If not an int, will fall to catch + val value = text.toInt() + + // Make sure value is in range + if (value in minValue..maxValue) + return true + } catch (e: NumberFormatException) { + } + + return false + } + + fun setMinValue(minValue: Int) { + this.minValue = minValue + } + + fun setMaxValue(maxValue: Int) { + this.maxValue = maxValue + } +} + +/** + * A filter that only accepts integer and decimal number values (Doubles) + */ +class MyDoubleFilter : MyDocFilter() { + + // Limit input to a specific range + var minValue = Double.NEGATIVE_INFINITY + var maxValue = Double.MAX_VALUE + + // Maximum digits allowed before the decimal point + var maxIntDigits = Int.MAX_VALUE + // Maximum digits allowed after the decimal point + var maxPrecision = Int.MAX_VALUE + // Max total digits allowed + var maxTotalDigits = Int.MAX_VALUE + + // Find number of integer places. Answer differs based on if a decimal point and/or negative sign is present + fun getIntegerDigits(text: String) : Int { + + val decimalPos = text.indexOf('.') + + return when { + // If it contains a Decimal AND - sign + decimalPos != -1 && text[0] == '-' -> + decimalPos - 1 + // If it contains Decimal but no - sign + decimalPos != -1 -> + decimalPos + // If it contains - sign but no decimal + text[0] == '-' -> + text.length - 1 + // If it contains no decimal or - sign + else -> + text.length + } + } + + // Find number of decimal places. Answer differs based on if a decimal point and/or negative sign is present + fun getDecimalPrecision(text: String) : Int { + + // If text has a decimal point + return if(text.contains(".")) { + + val integerPlaces = getIntegerDigits(text) + + // If also has negative sign + if(text[0] == '-') + text.length - integerPlaces - 2 + else + text.length - integerPlaces - 1 + } + // If no decimal point, no decimal precision + else + 0 + } + + override fun test(text: String): Boolean { + try { + + // Accept empty input + if(text == "") + return true + + // Accept a decimal point alone, and/or negative sign + if (text == "." || ((text == "-" || text == "-.") && minValue < 0)) + return true + + val integerPlaces = getIntegerDigits(text) + val decimalPlaces = getDecimalPrecision(text) + val totalDigits = integerPlaces + decimalPlaces + + // A "d" or "f" at the end of a number is accepted, as this indicates how to format the number. + // Also accepts a space (" "). We do not want this, so explicitly reject these scenarios. + // Also, check max precision, max int digits, and max total digits + if (text.toLowerCase(Locale.ROOT).contains("d") || text.toLowerCase(Locale.ROOT).contains("f") || + text.contains(" ") || decimalPlaces > maxPrecision || integerPlaces > maxIntDigits || + totalDigits > maxTotalDigits) + return false + + // If not a double, will fall to catch + val value = text.toDouble() + + // Make sure value is in range + if (value in minValue..maxValue) + return true + } catch (e: NumberFormatException) { + } + + return false + } +} \ No newline at end of file diff --git a/src/MyScreen.kt b/src/MyScreen.kt new file mode 100644 index 0000000..79afa3f --- /dev/null +++ b/src/MyScreen.kt @@ -0,0 +1,12 @@ +import javax.swing.JFrame +import javax.swing.JPanel + +open class MyScreen() { + + var parent : JFrame = JFrame() + var panel: JPanel = JPanel() + + constructor(parent : JFrame) : this() { + this.parent = parent + } +} \ No newline at end of file diff --git a/src/MyTable.kt b/src/MyTable.kt new file mode 100644 index 0000000..360f13f --- /dev/null +++ b/src/MyTable.kt @@ -0,0 +1,102 @@ + +import java.util.regex.Matcher +import java.util.regex.Pattern +import javax.swing.JTable +import javax.swing.table.TableModel +import javax.swing.table.TableRowSorter + +class MyTable : JTable() { + fun hideColumn(columnName: String) { + getColumn(columnName).minWidth = 0 // Must be set before maxWidth!! + getColumn(columnName).maxWidth = 0 + getColumn(columnName).width = 0 + } + + fun addSort(numberColumns: ArrayList? = null, currencyColumns: ArrayList? = null) { + val sorter: TableRowSorter = TableRowSorter() + rowSorter = sorter + sorter.model = model + val textColumns = ArrayList() + textColumns.add(1) + + val numComparator = Comparator { name1: String, name2: String -> + try { + val one = name1.toDouble() + val two = name2.toDouble() + one.compareTo(two) + } catch (e: NumberFormatException) { + 0 + } + } as Comparator + + val currencyComparator = Comparator { name1: String, name2: String -> + if (name1 == " " || name2 == " " || + name1.startsWith("") || name2.startsWith("") + ) + 0 + else { + // will filter out the numbers from the currency symbols + val matcher1: Matcher = Pattern.compile("\\d+[.]*\\d*").matcher(name1) + val matcher2: Matcher = Pattern.compile("\\d+[.]*\\d*").matcher(name2) + matcher1.find() + matcher2.find() + + matcher1.group().toDouble().compareTo(matcher2.group().toDouble()) + } + } as Comparator + + val textComparator = Comparator { name1: String, name2: String -> + if (name1 == " " || name2 == " " || + name1.startsWith("") || name2.startsWith("")) + 0 + else { + name1.compareTo(name2) + } + } as Comparator + + // Add all non-number rows to the list of text rows + for (i in 0 until columnCount) + textColumns.add(i) + + if(numberColumns != null) { + // Number based columns + for (num in numberColumns) { + sorter.setComparator(num, numComparator) + textColumns.remove(num) + } + } + + if(currencyColumns != null) { + for (num in currencyColumns) { + sorter.setComparator(num, currencyComparator) + textColumns.remove(num) + } + } + + // Text based columns + for (num in textColumns) { + sorter.setComparator(num, textComparator) + } + } + + fun resizeColumns() { + for (column in 0 until columnCount) { + val tableColumn = columnModel.getColumn(column) + var preferredWidth = tableColumn.minWidth + val maxWidth = tableColumn.maxWidth + for (row in 0 until rowCount) { + val cellRenderer = getCellRenderer(row, column) + val c = prepareRenderer(cellRenderer, row, column) + val width = c.preferredSize.width + intercellSpacing.width + preferredWidth = Math.max(preferredWidth, width) + + // We've exceeded the maximum width, no need to check other rows + if (preferredWidth >= maxWidth) { + preferredWidth = maxWidth + break + } + } + tableColumn.preferredWidth = preferredWidth + } + } +} \ No newline at end of file diff --git a/src/NewContainerDialog.form b/src/NewContainerDialog.form new file mode 100644 index 0000000..675486b --- /dev/null +++ b/src/NewContainerDialog.form @@ -0,0 +1,127 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/NewContainerDialog.java b/src/NewContainerDialog.java new file mode 100644 index 0000000..90c34e8 --- /dev/null +++ b/src/NewContainerDialog.java @@ -0,0 +1,83 @@ +import items.Container; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class NewContainerDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JTextField nameInput; + private JComboBox parentBox; + private JTextArea errorDisplay; + private JComboBox locationsBox; + + private final NumismatistAPI api; + + public NewContainerDialog(JFrame parent) { + setContentPane(contentPane); + setModal(true); + setResizable(false); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + getRootPane().setDefaultButton(buttonOK); + setIconImage(Main.getIcon().getImage()); + + setTitle(Main.getString("addContainer_title")); + + api = ((Main) parent).api; + + ComboBoxHelper.setContainerList(parentBox, api); + + buttonOK.addActionListener(e -> onOK()); + + buttonCancel.addActionListener(e -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + Container container = new Container(); + + if(nameInput.getText() != null && !nameInput.getText().equals("")) { + container.setName(nameInput.getText()); + if(parentBox.getSelectedItem() != null && !parentBox.getSelectedItem().toString().equals("")) { + container.setParentID(api.findContainer(parentBox.toString()).getId()); + } + + int result = container.saveToDb(api); + if(result != 1) + errorDisplay.setText(api.getSuccessMessage(result)); + else { + // Add result to locationBox + if(locationsBox != null) { + locationsBox.addItem(container.getName()); + locationsBox.setSelectedItem(container.getName()); + locationsBox.invalidate(); + } + dispose(); + } + } + else { + errorDisplay.setText(Main.getString("addContainer_error_emptyName")); + } + + } + + void setLocationsBox(JComboBox locationsBox) { + this.locationsBox = locationsBox; + } + + private void onCancel() { + dispose(); + } +} diff --git a/src/NewFolderDialog.form b/src/NewFolderDialog.form new file mode 100644 index 0000000..6a5f1d9 --- /dev/null +++ b/src/NewFolderDialog.form @@ -0,0 +1,170 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/NewFolderDialog.java b/src/NewFolderDialog.java new file mode 100644 index 0000000..0317d34 --- /dev/null +++ b/src/NewFolderDialog.java @@ -0,0 +1,198 @@ +import items.Book; +import items.BookPage; + +import javax.swing.*; +import javax.swing.text.PlainDocument; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.text.MessageFormat; + +public class NewFolderDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JTextField titleInput; + private JTextField pagesInput; + private JTextField denominationInput; + private JTextField startYearInput; + private JTextField endYearInput; + private JButton importButton; + private JButton preMadeButton; + + private final JFrame parent; + + public NewFolderDialog(JFrame parent) { + super(parent); + setContentPane(contentPane); + setModal(true); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + setResizable(false); + getRootPane().setDefaultButton(buttonOK); + + //NumismatistAPI api = ((Main)parent).api; + + this.parent = parent; + + setTitle(Main.getString("newFolder_title")); + + buttonOK.addActionListener(e -> onOK()); + buttonCancel.addActionListener(e -> onCancel()); + + // Set input filters + MyIntFilter pagesFilter = new MyIntFilter(); + pagesFilter.setMinValue(1); + pagesFilter.setMaxDigits(2); + ((PlainDocument) pagesInput.getDocument()).setDocumentFilter(pagesFilter); + + ((PlainDocument) startYearInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getCurrentYearFilter()); + ((PlainDocument) endYearInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getCurrentYearFilter()); + + ((PlainDocument) denominationInput.getDocument()).setDocumentFilter(MyDocFilter.Companion.getDenominationFilter()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + importButton.addActionListener(e -> { + final JFileChooser fc = new JFileChooser(); + + fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + fc.setFileFilter(new XmlFilter()); + fc.setCurrentDirectory(new File(NumismatistAPI.Companion.getResPath("xml", "books"))); + + int returnVal = fc.showOpenDialog(parent); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + + BookPage page = ((Main) parent).api.importBook(file.getAbsolutePath()).getPages().get(0); + BookPageDisplay pageDisplay = new BookPageDisplay(parent, page); + ((Main) parent).changeScreen(pageDisplay.getPanel(), MessageFormat.format(Main.getString("book_title"), + page.getBook().getTitle() ,1)); + + dispose(); + } + }); + + preMadeButton.addActionListener(e -> { + PremadeFolderDialog folderDialog = new PremadeFolderDialog(parent); + folderDialog.pack(); + folderDialog.setLocationRelativeTo(this); + folderDialog.setVisible(true); + + if(!folderDialog.cancelled) + dispose(); + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + int startYear = 0; + int endYear = 0; + + try { + startYear = Integer.parseInt(startYearInput.getText()); + } + catch (NumberFormatException ignored) { + } + + try { + endYear = Integer.parseInt(endYearInput.getText()); + } + catch (NumberFormatException ignored) { + + } + + String title = ""; + String errorMessage = ""; + + if(titleInput.getText().isBlank()) { + errorMessage += Main.getString("newFolder_error_emptyTitle"); + } + else { + title = titleInput.getText(); + } + + int pages = 0; + + if(pagesInput.getText().isBlank()) { + + if(!errorMessage.equals("")) { + errorMessage += "\n"; + } + errorMessage += Main.getString("newFolder_error_emptyPages"); + } + else { + pages = Integer.parseInt(pagesInput.getText()); + } + + if(startYear > endYear && endYear != 0) { + if(!errorMessage.equals("")) { + errorMessage += "\n"; + } + errorMessage += Main.getString("newFolder_error_startYearGreater"); + } + else if(startYear == 0 && endYear != 0) { + if(!errorMessage.equals("")) { + errorMessage += "\n"; + } + errorMessage += Main.getString("newFolder_error_startYearEmpty"); + } + + // Input is valid + if(errorMessage.equals("")) { + + Book book = new Book(); + book.setTitle(title); + + book.setStartYear(startYear); + book.setEndYear(endYear); + + try { + if (!denominationInput.getText().isBlank()) { + book.setDenomination(Double.parseDouble(denominationInput.getText())); + } + } + catch (NumberFormatException nfe) { + book.setDenomination(-1); + } + + // Add pages to book + for(int i = 0; i < pages; i++) { + BookPage page = new BookPage(); + book.addPage(page); + page.setBook(book); + page.setPageNum(i+1); + } + + // Show new page dialog + NewFolderPageDialog newPageDialog = new NewFolderPageDialog(parent, book, 1); + newPageDialog.pack(); + newPageDialog.setLocationRelativeTo(this); + newPageDialog.setVisible(true); + + if(!newPageDialog.cancelled) + dispose(); + } + else { + JOptionPane.showMessageDialog(parent, errorMessage, + Main.getString("newFolder_error_title"), JOptionPane.ERROR_MESSAGE); + } + } + + private void onCancel() { + dispose(); + } + + private void createUIComponents() { + + + } +} diff --git a/src/NewFolderPageDialog.form b/src/NewFolderPageDialog.form new file mode 100644 index 0000000..e0bd3ad --- /dev/null +++ b/src/NewFolderPageDialog.form @@ -0,0 +1,148 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/NewFolderPageDialog.java b/src/NewFolderPageDialog.java new file mode 100644 index 0000000..bf0d452 --- /dev/null +++ b/src/NewFolderPageDialog.java @@ -0,0 +1,210 @@ +import items.Book; +import items.BookPage; +import items.PageSlot; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.text.MessageFormat; +import java.util.ArrayList; + +public class NewFolderPageDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JCheckBox alternatingCheckbox; + private JCheckBox customCheckbox; + private JComboBox sizeDropDown; + private JSpinner rowsSpinner; + private JSpinner perRowSpinner; + private JSpinner perRowSpinner2; + + private final JFrame parent; + private final Book book; + private final int pageNum; + + private final BookPage page; + + boolean cancelled = false; + + public NewFolderPageDialog(JFrame parent, Book book, int pageNum) { + super(parent); + setContentPane(contentPane); + setModal(true); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + getRootPane().setDefaultButton(buttonOK); + setResizable(false); + + this.parent = parent; + this.book = book; + this.pageNum = pageNum; + + this.page = new BookPage(); + + setTitle(MessageFormat.format(Main.getString("newPage_title"), book.getTitle(), pageNum)); + + buttonOK.addActionListener(e -> onOK()); + + buttonCancel.addActionListener(e -> onCancel()); + + sizeDropDown.addItem(Main.getString("newPage_dropdown_cent")); + sizeDropDown.addItem(Main.getString("newPage_dropdown_nickel")); + sizeDropDown.addItem(Main.getString("newPage_dropdown_dime")); + sizeDropDown.addItem(Main.getString("newPage_dropdown_quarter")); + sizeDropDown.addItem(Main.getString("newPage_dropdown_half")); + sizeDropDown.addItem(Main.getString("newPage_dropdown_dollar")); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + // Handle checkbox clicks + customCheckbox.addActionListener(e -> { + if(customCheckbox.isSelected()) { + alternatingCheckbox.setEnabled(false); + perRowSpinner.setEnabled(false); + perRowSpinner2.setEnabled(false); + } + else { + alternatingCheckbox.setEnabled(true); + perRowSpinner.setEnabled(true); + if(alternatingCheckbox.isSelected()) + perRowSpinner2.setEnabled(true); + } + }); + + alternatingCheckbox.addActionListener(e -> perRowSpinner2.setEnabled(alternatingCheckbox.isSelected())); + } + + private void onOK() { + + int rows = (Integer)rowsSpinner.getValue(); + int perRow = (Integer)perRowSpinner.getValue(); + int perRow2 = (Integer)perRowSpinner2.getValue(); + + ArrayList currentRow; + + String errorMessage = ""; + + // Validate input + if(rows < 1) { + errorMessage += Main.getString("newPage_error_invalidRows"); + } + if(perRow < 1) { + if(!errorMessage.equals("")) { + errorMessage += "\n"; + } + errorMessage += Main.getString("newPage_error_invalidCoins"); + } + if(alternatingCheckbox.isSelected() && perRow2 < 1) { + if(!errorMessage.equals("")) { + errorMessage += "\n"; + } + errorMessage += Main.getString("newPage_error_invalidRow2"); + } + + // If input not valid + if(!errorMessage.equals("")) { + JOptionPane.showMessageDialog(parent, errorMessage, + Main.getString("newPage_error_title"), JOptionPane.ERROR_MESSAGE); + } + // If input is valid + else { + + String sizeStr = sizeDropDown.getSelectedItem().toString(); + int size; + + if(sizeStr.equals(Main.getString("newPage_dropdown_cent"))) + size = PageSlot.SIZE_PENNY; + else if(sizeStr.equals(Main.getString("newPage_dropdown_nickel"))) + size = PageSlot.SIZE_NICKEL; + else if(sizeStr.equals(Main.getString("newPage_dropdown_dime"))) + size = PageSlot.SIZE_DIME; + else if(sizeStr.equals(Main.getString("newPage_dropdown_quarter"))) + size = PageSlot.SIZE_QUARTER; + else if(sizeStr.equals(Main.getString("newPage_dropdown_half"))) + size = PageSlot.SIZE_HALF; + else + size = PageSlot.SIZE_DOLLAR; + + // for each row + for (int i = 0; i < rows; i++) { + + currentRow = new ArrayList<>(); + + // If all rows are the same or if variable i is even + if (!customCheckbox.isSelected() && (!alternatingCheckbox.isSelected() || i % 2 == 0)) { + for (int c = 0; c < perRow; c++) { + PageSlot slot = new PageSlot(); + slot.setSize(size); + slot.setBookPage(page); + slot.setRowNum(i+1); + slot.setColNum(c+1); + + currentRow.add(slot); + } + } + // If on alternating row + else if (!customCheckbox.isSelected()) { + for (int c = 0; c < perRow2; c++) { + PageSlot slot = new PageSlot(); + slot.setSize(size); + slot.setBookPage(page); + slot.setRowNum(i+1); + slot.setColNum(c+1); + + currentRow.add(slot); + } + } + page.getRows().add(currentRow); + } + + page.setPageNum(pageNum); + page.setBook(book); + book.getPages().set(pageNum - 1, page); + + // TODO: Handle custom page + + if(pageNum == book.getPages().size()) { + //TODO: save book and pages + + //show book + BookPageDisplay pageDisplay = new BookPageDisplay(parent, book.getPages().get(0)); + ((Main) parent).changeScreen(pageDisplay.getPanel(), + MessageFormat.format(Main.getString("book_title"), book.getTitle(), pageNum)); + + dispose(); + } + //Go to next page if there are more + else { + NewFolderPageDialog newPageDialog = new NewFolderPageDialog(parent, book, pageNum +1); + newPageDialog.pack(); + newPageDialog.setLocationRelativeTo(this); + // Hide for now + setVisible(false); + newPageDialog.setVisible(true); + + // Remove is next dialog is finished + if(!newPageDialog.cancelled) + dispose(); + // Show if next dialog was cancelled + else + setVisible(true); + } + } + } + + private void onCancel() { + + cancelled = true; + // add your code here if necessary + dispose(); + } +} diff --git a/src/PageSlotPanel.kt b/src/PageSlotPanel.kt new file mode 100644 index 0000000..1c2240d --- /dev/null +++ b/src/PageSlotPanel.kt @@ -0,0 +1,370 @@ +import items.PageSlot +import javafx.scene.input.KeyCode +import java.awt.Component +import java.awt.Dimension +import java.awt.event.* +import javax.swing.* + + +// A JPanel for displaying a coin slot, including 2 lines of text for year and mintage, or whatever else may be there +class PageSlotPanel(private var buttonSize: Int = PageSlot.SIZE_DOLLAR, line1Text: String = "", line2Text: String = "") : JPanel() { + + lateinit var page: BookPageDisplay + val radioButton = JRadioButton() + private val line1 = JLabel() + private val line2 = JLabel() + private val line1Input = JTextField() + private val line2Input = JTextField() + + private val clickToChange = Main.getString("book_clickToChange") + + var slot = PageSlot() + set(value) { + + val oldSize = field.size + field = value + + if(value.size == -1) + setSlotSize() + + if(oldSize != value.size) + // Resize all icons and set them + setIcons() + } + + init { + + // Set layout to a vertical box layout + val boxLayout = BoxLayout(this, BoxLayout.Y_AXIS) + layout = boxLayout + // set margins around the slot - to get spacing between slots + border = BorderFactory.createEmptyBorder(10,10,10,10) + + // Center each item before adding + radioButton.alignmentX = Component.CENTER_ALIGNMENT + add(radioButton) + + line1.alignmentX = Component.CENTER_ALIGNMENT + add(line1) + line1.text = line1Text.ifBlank { clickToChange } + + line1Input.setSize(line1.width, line1.height) + add(line1Input) + line1Input.isVisible = false + + line2.alignmentX = Component.CENTER_ALIGNMENT + add(line2) + line2.text = line2Text.ifBlank { clickToChange } + + line2Input.setSize(line2.width, line2.height) + add(line2Input) + line2Input.isVisible = false + + // Add enter key listener + line1Input.addActionListener { + run { + slot.line1Text = line1Input.text + + if(line1Input.text.isNotBlank()) { + line1.text = line1Input.text + } + else { + line1.text = clickToChange + } + + line1Input.isVisible = false + line1.isVisible = true + } + } + + // Add escape key listener + line1Input.addKeyListener(object : KeyListener { + override fun keyTyped(e: KeyEvent?) { + } + + override fun keyPressed(e: KeyEvent?) { + if(e?.keyCode == KeyCode.ESCAPE.code) { + line1Input.isVisible = false + line1.isVisible = true + } + } + + override fun keyReleased(e: KeyEvent?) { + } + + }) + + // Add enter key listener + line2Input.addActionListener { + run { + slot.line2Text = line2Input.text + + if(line2Input.text.isNotBlank()) { + line2.text = line2Input.text + } + else + line2.text = clickToChange + + line2Input.isVisible = false + line2.isVisible = true + } + } + + // Add escape key listener + line2Input.addKeyListener(object : KeyListener { + override fun keyTyped(e: KeyEvent?) { + } + + override fun keyPressed(e: KeyEvent?) { + if(e?.keyCode == KeyCode.ESCAPE.code) { + line2Input.isVisible = false + line2.isVisible = true + } + } + + override fun keyReleased(e: KeyEvent?) { + } + + }) + + // Crate a mouse listener to listen for mouse over and clicks on labels and panel + // This will change the icon for the radio button and perform a click on the radio button + val mouseAdapter = object: MouseAdapter() { + override fun mouseClicked(e: MouseEvent?) { + + // If right-clicked + if(SwingUtilities.isRightMouseButton(e)) { + + // Create right click menu + val rightClickMenu = JPopupMenu() + val edit = JMenuItem(Main.getString("book_rightClick_edit")) + + if(e?.source == line1) { + // Create edit item + edit.addActionListener { + + val maxWidth = if(line1.text == clickToChange) + line1.width + else + line1.width + 25 + + // Allow Edit + line1Input.maximumSize = Dimension(maxWidth, line1.height) + line1.isVisible = false + if(line1Text != clickToChange) + line1Input.text = line1.text + line1Input.isVisible = true + line1Input.grabFocus() + } + + rightClickMenu.add(edit) + + // Show the menu at the location of the click + rightClickMenu.show(line1, e.point.x, e.point.y) + } + else if(e?.source == line2) { + + val maxWidth = if(line2.text == clickToChange) + line2.width + else + line2.width + 25 + + // Create edit item + edit.addActionListener { + // Allow Edit + line2Input.maximumSize = Dimension(maxWidth, line2.height) + line2.isVisible = false + if(line2Text != clickToChange) + line2Input.text = line2.text + line2Input.isVisible = true + line2Input.grabFocus() + } + + rightClickMenu.add(edit) + + // Show the menu at the location of the click + rightClickMenu.show(line2, e.point.x, e.point.y) + } + } + // If it's a label, and label is empty, change label + else if(e?.source == line1 && slot.line1Text.isBlank()) { + line1Input.maximumSize = Dimension(line1.width, line1.height) + line1.isVisible = false + line1Input.isVisible = true + line1Input.grabFocus() + } + else if(e?.source == line2 && slot.line2Text.isBlank()) { + line2Input.maximumSize = Dimension(line2.width, line2.height) + line2.isVisible = false + line2Input.isVisible = true + line2Input.grabFocus() + } + // Handle the click + else { + // If slot is empty + if(!radioButton.isSelected) { + val options1 = arrayOf(Main.getString("book_clickMenu_newCoin"), + Main.getString("book_clickMenu_existingCoin"), + Main.getString("cancel")) + + val result = JOptionPane.showOptionDialog(null, + Main.getString("book_clickMenu_emptyMessage"), + Main.getString("book_clickMenu_emptyTitle"), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options1, options1[2]) + if (result == JOptionPane.YES_OPTION) { + // TODO: Show New Coin Screen + + radioButton.doClick() + } + if(result == JOptionPane.NO_OPTION) { + // TODO: Show Existing coin selection + + radioButton.doClick() + } + } + else { + // Display "Remove from collection", "Remove from Book", "Cancel" Dialog + val options1 = arrayOf(Main.getString("book_clickMenu_removeColl"), + Main.getString("book_clickMenu_removeBook"), + Main.getString("cancel")) + + val result = JOptionPane.showOptionDialog(null, + Main.getString("book_clickMenu_filledMessage"), + Main.getString("book_clickMenu_filledTitle"), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options1, options1[2]) + // Remove from collection + if (result == JOptionPane.YES_OPTION) { + val remove = JOptionPane.showConfirmDialog(null, + Main.getString("book_dialog_sureMessage"), + Main.getString("book_dialog_sureTitle"), + JOptionPane.YES_NO_OPTION) + + if(remove == JOptionPane.YES_OPTION) { + // Add coin to list to be removed from collection + slot.bookPage.coinsToDelete.add(slot.coin!!) + slot.removeCoin() + + radioButton.doClick() + } + } + // Remove from book + if(result == JOptionPane.NO_OPTION) { + slot.removeCoin() + + radioButton.doClick() + } + } + + setIcons(true) + } + } + override fun mouseEntered(e: MouseEvent?) { + // If on a label, and label can change + if((e?.source == line1 && slot.line1Text.isBlank()) || + (e?.source == line2 && slot.line2Text.isBlank())) { + // Don't highlight + } + else + setIcons(true) + } + + override fun mouseExited(e: MouseEvent?) { + setIcons() + } + } + + // Apply the mouse listener to all items + line1.addMouseListener(mouseAdapter) + line2.addMouseListener(mouseAdapter) + addMouseListener(mouseAdapter) + for(listener in radioButton.mouseListeners) { + radioButton.removeMouseListener(listener) + } + radioButton.addMouseListener(mouseAdapter) + } + + /** + * Resize all icons and set them + * + * @param hovered True if something is being hovered over, otherwise false + */ + private fun setIcons(hovered: Boolean = false) { + + // TODO: Create disabled icons? + radioButton.rolloverIcon = page.bookPageHelper.getIcon(BookPageHelper.MODE_EMPTY_HOVERED, buttonSize) + + if(hovered) + radioButton.icon = page.bookPageHelper.getIcon(BookPageHelper.MODE_EMPTY_HOVERED, buttonSize) + else { + radioButton.icon = page.bookPageHelper.getIcon(BookPageHelper.MODE_EMPTY, buttonSize) + } + + when (buttonSize) { + PageSlot.SIZE_PENNY -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_PENNY), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, + PageSlot.SIZE_PENNY), hovered) + } + PageSlot.SIZE_NICKEL -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_NICKEL), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, PageSlot.SIZE_NICKEL), hovered) + } + PageSlot.SIZE_DIME -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_DIME), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, PageSlot.SIZE_DIME), hovered) + } + PageSlot.SIZE_QUARTER -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_QUARTER), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, PageSlot.SIZE_QUARTER), hovered) + } + PageSlot.SIZE_HALF -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_HALF), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, PageSlot.SIZE_HALF), hovered) + } + else -> { + setSelectedIcon(page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED, PageSlot.SIZE_DOLLAR), + page.bookPageHelper.getIcon(BookPageHelper.MODE_FILLED_HOVERED, PageSlot.SIZE_DOLLAR), hovered) + } + + } + } + + /** + * sets icons for when something is hovered over and when it's selcted + * + * @param icon icon to display when not hovered + * @param hoverIcon icon to display when hovering + * @param hovered whether it is currently being hovered over + */ + private fun setSelectedIcon(icon: Icon, hoverIcon: Icon, hovered: Boolean) { + + radioButton.rolloverSelectedIcon = hoverIcon + + if(hovered) + radioButton.selectedIcon = hoverIcon + else { + radioButton.selectedIcon = icon + } + } + + /** + * If slot has a size, use that for button size. Otherwise, use denomination from book to determine size + */ + private fun setSlotSize() { + if(slot.size == -1) { + buttonSize = when (slot.bookPage.book.denomination) { + .01 -> PageSlot.SIZE_PENNY + .05 -> PageSlot.SIZE_NICKEL + .25 -> PageSlot.SIZE_QUARTER + .5 -> PageSlot.SIZE_HALF + else -> PageSlot.SIZE_DOLLAR + } + slot.size = buttonSize + } + else { + buttonSize = slot.size + } + } +} \ No newline at end of file diff --git a/src/ExistingCoinDialog.form b/src/PremadeFolderDialog.form similarity index 81% rename from src/ExistingCoinDialog.form rename to src/PremadeFolderDialog.form index 7574149..ecf15b3 100644 --- a/src/ExistingCoinDialog.form +++ b/src/PremadeFolderDialog.form @@ -1,5 +1,5 @@ -
+ @@ -34,7 +34,7 @@ - + @@ -42,14 +42,14 @@ - + - + @@ -57,20 +57,24 @@ - + - + - + + + + + + + - + - - - + diff --git a/src/PremadeFolderDialog.java b/src/PremadeFolderDialog.java new file mode 100644 index 0000000..bbd2c7a --- /dev/null +++ b/src/PremadeFolderDialog.java @@ -0,0 +1,78 @@ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; + +public class PremadeFolderDialog extends JDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JList list; + + private final JFrame parent; + + boolean cancelled = false; + + private final NumismatistAPI api; + + public PremadeFolderDialog(JFrame parent) { + super(parent); + api = ((Main) parent).api; + setContentPane(contentPane); + setModal(true); + setTitle("Select a folder"); + setModalityType(Dialog.DEFAULT_MODALITY_TYPE); + getRootPane().setDefaultButton(buttonOK); + setResizable(false); + + this.parent = parent; + + buttonOK.addActionListener(e -> onOK()); + + buttonCancel.addActionListener(e -> onCancel()); + + // call onCancel() when cross is clicked + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // call onCancel() on ESCAPE + contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + + String selected = NumismatistAPI.Companion.getResPath("xml", "books") + list.getSelectedValue().toString(); + + BookPageDisplay pageDisplay = + new BookPageDisplay(parent, api.importBook(selected).getPages().get(0)); + + ((Main) parent).changeScreen(pageDisplay.getPanel(), "Page " + 1); + + // add your code here + dispose(); + } + + private void onCancel() { + cancelled = true; + // add your code here if necessary + dispose(); + } + + private void createUIComponents() { + // Create list of files in custom books + ArrayList paths = new ArrayList<>(); + try { + paths = Main.getResourcesList("xml/books"); + } + catch (Exception e) { + e.printStackTrace(); + } + + list = new JList(paths.toArray()); + } +} diff --git a/src/SortedComboBoxModel.java b/src/SortedComboBoxModel.java new file mode 100644 index 0000000..da598a3 --- /dev/null +++ b/src/SortedComboBoxModel.java @@ -0,0 +1,39 @@ +import javax.swing.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Vector; + +public class SortedComboBoxModel extends DefaultComboBoxModel { + public SortedComboBoxModel() { + super(); + } + public SortedComboBoxModel(Object[] items) { + Arrays.sort(items); + int size = items.length; + for (int i = 0; i < size; i++) { + super.addElement(items[i]); + } + setSelectedItem(items[0]); + } + public SortedComboBoxModel(Vector items) { + Collections.sort(items); + int size = items.size(); + for (int i = 0; i < size; i++) { + super.addElement(items.elementAt(i)); + } + setSelectedItem(items.elementAt(0)); + } + public void addElement(Object element) { + insertElementAt(element, 0); + } + public void insertElementAt(Object element, int index) { + int size = getSize(); + for (index = 0; index < size; index++) { + Comparable c = (Comparable) getElementAt(index); + if (c.compareTo(element) > 0) { + break; + } + } + super.insertElementAt(element, index); + } +} diff --git a/src/XmlFilter.kt b/src/XmlFilter.kt new file mode 100644 index 0000000..ba0e5e3 --- /dev/null +++ b/src/XmlFilter.kt @@ -0,0 +1,46 @@ +import java.io.File +import javax.swing.filechooser.FileFilter + +object XmlExtensions { + + val ACCEPTABLE_EXTENSIONS = arrayOf("xml") + /* + * Get the extension of a file. + */ + fun getExtension(f: File): String? { + var ext: String? = null + val s = f.name + val i = s.lastIndexOf('.') + if (i > 0 && i < s.length - 1) { + ext = s.substring(i + 1).toLowerCase() + } + return ext + } +} + +class XmlFilter : FileFilter() { + override fun accept(f: File): Boolean { + if (f.isDirectory) { + return true + } + + val extension = XmlExtensions.getExtension(f) + return if (extension != null) { + XmlExtensions.ACCEPTABLE_EXTENSIONS.contains(extension) + } else false + } + + override fun getDescription(): String { + var string = "XML File (" + + for((count, extension) in XmlExtensions.ACCEPTABLE_EXTENSIONS.withIndex()) { + if (count > 0) + string += ", " + string += "*.$extension" + } + + string += ")" + + return string + } +} \ No newline at end of file diff --git a/src/items/Bill.kt b/src/items/Bill.kt deleted file mode 100644 index 11925fa..0000000 --- a/src/items/Bill.kt +++ /dev/null @@ -1,182 +0,0 @@ -package items - -import java.sql.ResultSet -import java.sql.SQLException -import java.util.* -import kotlin.collections.ArrayList - -class Bill{ - - var id = 0 - var country = "US" - var year = 0 - var seriesLetter = "" - var denomination = 0.0 - var value = 0.0 - var graded = false - var condition = "" - var name = "" - var serial = "" - var signatures = "" - var star = false - var error = false - var errorType = "" - var note = "" - var district = "" - var plateSeriesObv = "" - var plateSeriesRev = "" - var notePosition = "" - var obvImgExt = "" - var revImgExt = "" - - companion object { - - val CONDITIONS = arrayOf("", "G-4", "VG-8", "F-12", "VF-20", "EF-40", "AU-50", "UC-63") - - fun getBillsFromSql(connection: DatabaseConnection, sql: String = "") : ArrayList { - val bills = ArrayList() - - val query = if (sql == "") - """ - SELECT * - FROM Bills - ORDER BY ID; - """.trimIndent() - else - sql - - val results: ResultSet? = connection.runQuery(query) - - // Show bills - try { - if (results != null) { - // Find out how many rows are in the result - var size = 0 - try { - results.last() - size = results.row - results.beforeFirst() - } catch (ex: Exception) { - ex.printStackTrace() - } - - // for each bill found - for (i in 0 until size) { - results.next() - - val newBill = Bill() - newBill.id = results.getInt("ID") - newBill.country = Objects.requireNonNullElse(results.getString("Country"), "US") - newBill.name = Objects.requireNonNullElse(results.getString("Type"), "") - newBill.year = results.getInt("Yr") - newBill.seriesLetter = results.getString("SeriesLetter") - newBill.denomination = results.getDouble("Denomination") - newBill.value = results.getDouble("CurValue") - newBill.graded = results.getBoolean("Graded") - newBill.serial = results.getString("Serial") - newBill.condition = Objects.requireNonNullElse(results.getString("Grade"), "") - newBill.error = results.getBoolean("Error") - newBill.errorType = Objects.requireNonNullElse(results.getString("ErrorType"), "") - newBill.plateSeriesObv = Objects.requireNonNullElse(results.getString("PlateSeriesObv"), "") - newBill.plateSeriesRev = Objects.requireNonNullElse(results.getString("PlateSeriesRev"), "") - newBill.notePosition = Objects.requireNonNullElse(results.getString("NotePosition"), "") - newBill.star = results.getBoolean("Star") - newBill.note = Objects.requireNonNullElse(results.getString("Note"), "") - - if(results.getString("ObvImgExt") != null) - newBill.obvImgExt = results.getString("ObvImgExt") - if(results.getString("RevImgExt") != null) - newBill.revImgExt = results.getString("RevImgExt") - - bills.add(newBill) - } - } - } catch (e: SQLException) { - e.printStackTrace() - } - - return bills - } - } - - fun copy() : Bill { - val newBill = Bill() - - newBill.country = country - newBill.name = name - newBill.year = year - newBill.seriesLetter = seriesLetter - newBill.denomination = denomination - newBill.value = value - newBill.graded = graded - newBill.condition = condition - newBill.note = note - - return newBill - } - - fun saveToDb(connection: DatabaseConnection): Int { - val sql = if(id != 0) { - "UPDATE Bills SET Country=\"$country\", Type=\"$name\", Yr=$year, " + - "Denomination=$denomination, CurValue=$value, Graded=$graded, Grade=\"$condition\", " + - "SeriesLetter=\"$seriesLetter\", Serial=\"$serial\", Signatures=\"$signatures\", " + - "Error=$error, ErrorType=\"$errorType\", Note=\"$note\", " + - "PlateSeriesObv=\"$plateSeriesObv\", PlateSeriesRev=\"$plateSeriesRev\", " + - "NotePosition=\"$notePosition\", District=\"$district\", Star=$star\n" + - "WHERE ID=$id;" - } - else { - "INSERT INTO Bills(Country, Type, Yr, SeriesLetter, Denomination, CurValue, Graded, Grade, Error, " + - "ErrorType, Serial, Signatures, Note, PlateSeriesObv, PlateSeriesRev, NotePosition, " + - "District, Star)\n" + - "VALUES(\"$country\", \"$name\", $year, \"$seriesLetter\", $denomination, $value, " + - "$graded, \"$condition\", $error, \"$errorType\", \"$serial\", \"$signatures\", \"$note\", " + - "\"$plateSeriesObv\", \"$plateSeriesRev\", \"$notePosition\", \"$district\", $star);" - } - - return connection.runUpdate(sql) - } - - fun deleteFromDb(connection: DatabaseConnection) : Boolean { - - val sql = "DELETE FROM Bills WHERE ID=$id" - - val rows = connection.runUpdate(sql) - - if(rows == 1) - return true - - return false - } - - fun getImagePath(obverse: Boolean) : String { - val os = System.getProperty("os.name") - val slash = if (os.toLowerCase().contains("windows")) "\\" else "/" - - val path = Main.getSettingImagePath() + slash - - return if(obverse) - path + getObvImageName() + obvImgExt - else - path + getRevImageName() + revImgExt - } - - private fun getObvImageName() : String { - return "bill-$id-obv" - } - - private fun getRevImageName() : String { - return "bill-$id-rev" - } - - override fun toString(): String { - var out = "$year" - - if(seriesLetter != "") - out += "-$seriesLetter" - - out += " $name" - - return out - } -} \ No newline at end of file diff --git a/src/items/Coin.kt b/src/items/Coin.kt deleted file mode 100644 index b0d1d13..0000000 --- a/src/items/Coin.kt +++ /dev/null @@ -1,240 +0,0 @@ -package items - -import Main -import java.sql.ResultSet -import java.sql.SQLException -import java.util.* -import kotlin.collections.ArrayList - -class Coin -{ - companion object { - const val HALF_PENNY = 0.005 - const val PENNY = 0.01 - const val NICKEL = 0.05 - const val DIME = 0.10 - const val QUARTER = 0.25 - const val HALF_DOLLAR = 0.50 - const val DOLLAR = 1.0 - - val MINT_MARKS = arrayOf("", "C", "CC", "D", "O", "P", "S", "W") - val CONDITIONS = arrayOf("", "AG-3", "G-4", "VG-8", "F-12", "VF-20", "EF-40", "AU-50", "MS-60", "MS-63", "MS-65", "MS-66", "MS-67", "PF-67") - - fun getCoinsFromSql(connection: DatabaseConnection, sql: String = "") : ArrayList { - val coins = ArrayList() - - val query = if (sql == "") - """ - SELECT * - FROM Coins - WHERE SetID is null - ORDER BY ID; - """.trimIndent() - else - sql - - val results: ResultSet? = connection.runQuery(query) - - // Show coins - try { - if (results != null) { - // Find out how many rows are in the result - var size = 0 - try { - results.last() - size = results.row - results.beforeFirst() - } catch (ex: Exception) { - ex.printStackTrace() - } - - // for each coin found - for (i in 0 until size) { - results.next() - - val newCoin = Coin() - newCoin.id = results.getInt("ID") - newCoin.country = Objects.requireNonNullElse(results.getString("Country"), "US") - newCoin.name = Objects.requireNonNullElse(results.getString("Type"), "") - newCoin.year = results.getInt("Yr") - newCoin.denomination = results.getDouble("Denomination") - newCoin.value = results.getDouble("CurValue") - newCoin.mintMark = Objects.requireNonNullElse(results.getString("MintMark"), "") - newCoin.graded = results.getBoolean("Graded") - newCoin.condition = Objects.requireNonNullElse(results.getString("Grade"), "") - newCoin.error = results.getBoolean("Error") - newCoin.errorType = Objects.requireNonNullElse(results.getString("ErrorType"), "") - newCoin.note = Objects.requireNonNullElse(results.getString("Note"), "") - - if(results.getString("ObvImgExt") != null) - newCoin.obvImgExt = results.getString("ObvImgExt") - if(results.getString("RevImgExt") != null) - newCoin.revImgExt = results.getString("RevImgExt") - - coins.add(newCoin) - } - } - } catch (e: SQLException) { - e.printStackTrace() - } - - return coins - } - } - - fun copy() : Coin { - val newCoin = Coin() - - newCoin.id = 0 - newCoin.country = country - newCoin.year = year - newCoin.denomination = denomination - newCoin.mintMark = mintMark - newCoin.graded = graded - newCoin.condition = condition - newCoin.value = value - newCoin.note = note - newCoin.name = name - newCoin.setID = null - - newCoin.obvImgExt = "" - newCoin.revImgExt = "" - - return newCoin - } - - var id = 0 - var country = "US" - var year = 0 - var denomination = 0.0 - set(value) { - if(value > 0) - field = value - } - - var mintMark = "" - set(value) { - if(MINT_MARKS.contains(value)) - field = value - } - - var condition = "" - set(value) { - if(CONDITIONS.contains(value)) - field = value - } - - var graded = false - var name = "" - var value = 0.0 - set(value) { - if(value > 0) - field = value - } - - var error = false - var errorType = "" - var setID: Int? = null - var note = "" - - var obvImgExt = "" - var revImgExt = "" - - override fun toString() : String { - - var string = "$year" - if(mintMark != "") - string += "-$mintMark" - - string += " $name" - - return string - } - - fun saveToDb(connection: DatabaseConnection) : Int { - - val sql: String - - val rows: Int - - // set SetID to null if necessary - val newSetID = if(setID == null || setID == 0) - "null" - else - "" + setID - - if(id != 0) { - sql = "UPDATE Coins SET Country=\"" + country + "\"" + - ",Type=\"" + name + "\"" + - ",Yr=" + year.toString() + - ",Denomination=" + denomination.toString() + - ",CurValue=" + value.toString() + - ",MintMark=\"" + mintMark + "\"" + - ",Graded=" + graded.toString() + - ",Grade=\"" + condition + "\"" + - ",Error=" + error.toString() + - ",ErrorType=\"" + errorType + "\"" + - ",SetID=" + newSetID + - ",Note=\"" + note + "\"" + - "WHERE ID=" + id.toString() + ";" - - rows = connection.runUpdate(sql) - } - else { - sql = "INSERT INTO Coins(Country, Type, Yr, Denomination, CurValue, MintMark, Graded," + - " Grade, Error, ErrorType, SetID, Note)\n" + - "VALUES(\"$country\", \"$name\", $year, $denomination, $value, \"$mintMark\", $graded," + - " \"$condition\", $error, \"$errorType\", $newSetID, \"$note\");" - - rows = connection.runUpdate(sql) - - // If successful, set the new ID to this object - if(rows == 1) { - val results: ResultSet? = connection.runQuery("SELECT LAST_INSERT_ID();") - try { - // Find result of newly added set - if (results != null) { - results.next() - id = results.getInt("LAST_INSERT_ID()") - } - } catch (e: SQLException) { - e.printStackTrace() - } - } - } - - return rows - } - - fun getImagePath(obverse: Boolean) : String { - val os = System.getProperty("os.name") - val slash = if (os.toLowerCase().contains("windows")) "\\" else "/" - - val path = Main.getSettingImagePath() + slash - - return if(obverse) - path + getObvImageName() + obvImgExt - else - path + getRevImageName() + revImgExt - } - - private fun getObvImageName() : String { - return "coin-$id-obv" - } - - private fun getRevImageName() : String { - return "coin-$id-rev" - } - - fun deleteFromDb(connection: DatabaseConnection) : Boolean { - - val sql = "DELETE FROM Coins WHERE ID=$id" - - val rows = connection.runUpdate(sql) - - if(rows == 1) - return true - - return false - } -} \ No newline at end of file diff --git a/src/items/CoinSet.kt b/src/items/CoinSet.kt deleted file mode 100644 index 0169fe4..0000000 --- a/src/items/CoinSet.kt +++ /dev/null @@ -1,322 +0,0 @@ -package items - -import Main -import java.sql.ResultSet -import java.sql.SQLException -import java.util.* -import kotlin.collections.ArrayList - -class CoinSet { - - companion object { - fun getSetsFromSql(connection: DatabaseConnection, sql: String = "") : ArrayList { - val sets = ArrayList() - - val query = if (sql == "") - """ - SELECT * - FROM Sets - ORDER BY ID; - """.trimIndent() - else - sql - - val results: ResultSet? = connection.runQuery(query) - - // Show sets - try { - if (results != null) { - // Find out how many rows are in the result - var size = 0 - try { - results.last() - size = results.row - results.beforeFirst() - } catch (ex: Exception) { - ex.printStackTrace() - } - - // for each set found - for (i in 0 until size) { - results.next() - - val newSet = CoinSet() - newSet.id = results.getInt("ID") - newSet.name = Objects.requireNonNullElse(results.getString("Name"), "") - newSet.year = results.getInt("Yr") - newSet.value = results.getDouble("CurValue") - newSet.note = Objects.requireNonNullElse(results.getString("Note"), "") - - // Add coins to set - newSet.getCoinsFromSet() - - sets.add(newSet) - } - } - } - catch (e: SQLException) { - e.printStackTrace() - } - - return sets - } - } - - var id = 0 - var name = "" - var value = 0.0 - var year = 0 - var note = "" - var obvImgExt = "" - var revImgExt = "" - - val coins = ArrayList() - val removedCoins = ArrayList() - - fun getFaceValue() : Double { - var value = 0.0 - - for(coin in coins) - value += coin.denomination - - return value - } - - fun copy() : CoinSet { - val newSet = CoinSet() - - newSet.id = 0 - newSet.name = name - newSet.value = value - newSet.year = year - newSet.note = note - newSet.obvImgExt = "" - newSet.revImgExt = "" - - for(coin in coins) { - val newCoin = coin.copy() - newSet.addCoin(newCoin) - } - - return newSet - } - - fun saveToDb(connection: DatabaseConnection) : String { - val sql: String - var returnMessage = "" - - if(id != 0) { - - sql = "UPDATE Sets SET Name=\"$name\", Yr=$year, CurValue=$value, Note=\"$note\"\n" + - "WHERE ID=$id;" - - var rows = connection.runUpdate(sql) - if(rows == -1) { - returnMessage = "An error occurred while updating set." - } - else if (rows > 1) { - returnMessage = "Multiple row updated when updating set. This may mean something went wrong" - } - - var errors = 0 - - // Add coins that have been added - for (coin in coins) { - coin.setID = id - - rows = coin.saveToDb(connection) - if(rows == -1 && errors == 0) { - if(returnMessage != "") - returnMessage += "\n" - returnMessage += "An error occurred while adding one or more coins to the set" - errors++ - } - - } - - errors = 0 - - for (coin in removedCoins) { - coin.setID = null - - if(coin.id !=0) { - val coinRows = coin.saveToDb(connection) - if(coinRows == -1 && errors == 0) { - if(returnMessage != "") - returnMessage += "\n" - returnMessage += "An error occurred while removing one or more coins in the set" - errors++ - } - } - } - } - else { - sql = "INSERT INTO Sets(Name, Yr, CurValue, Note)\n" + - "VALUES(\"$name\", $year, $value, \"$note\");" - - val rows = connection.runUpdate(sql) - - // If successful, add coins to set - returnMessage = if(rows == 1) { - var errors = 0 - val results: ResultSet? = connection.runQuery("SELECT LAST_INSERT_ID();") - try { - // Find result of newly added set - if (results != null) { - results.next() - id = results.getInt("LAST_INSERT_ID()") - } - - // Add coins that have been added - for (coin in coins) { - coin.setID = id - - val coinRows = coin.saveToDb(connection) - if(coinRows != 1) - errors++ - } - - // Remove coins that have been removed - for (coin in removedCoins) { - coin.setID = 0 - - if(coin.id !=0) { - val coinRows = coin.saveToDb(connection) - if (coinRows != 1) - errors++ - } - } - } catch (e: SQLException) { - e.printStackTrace() - } - - if(errors == 0) - DatabaseConnection.SUCCESS_MESSAGE - else - DatabaseConnection.ERROR_MESSAGE - } - else if(rows == 0) { - DatabaseConnection.NO_CHANGE_MESSAGE - } - else if (rows == -1) { - DatabaseConnection.ERROR_MESSAGE - } - else { - DatabaseConnection.MULTIPLE_ROWS_MESSAGE - } - } - - return returnMessage - } - - fun deleteFromDb(connection: DatabaseConnection) : Boolean { - - getCoinsFromSet() - - val totalCoins = coins.size - var removedCoins = 0 - - // Remove all of the coins in the set - for (coin in coins) - if(coin.deleteFromDb(connection)) - removedCoins++ - - // If all of the individual coins were removed, remove the set - if(totalCoins == removedCoins) { - val sql = "DELETE FROM Sets WHERE ID=$id" - - val rows = connection.runUpdate(sql) - - if (rows == 1) - return true - } - - return false - } - - fun addCoin(coin: Coin) { - coins.add(coin) - } - - fun removeCoin(coin: Coin) : Boolean { - removedCoins.add(coin) - return coins.remove(coin) - } - - fun getImagePath(obverse: Boolean) : String { - val os = System.getProperty("os.name") - val slash = if (os.toLowerCase().contains("windows")) "\\" else "/" - - val path = Main.getSettingImagePath() + slash - - return if(obverse) - path + getObvImageName() + obvImgExt - else - path + getRevImageName() + revImgExt - } - - private fun getObvImageName() : String { - return "set-$id-obv" - } - - private fun getRevImageName() : String { - return "set-$id-rev" - } - - fun getCoinsFromSet() { - - coins.clear() - - val connection = DatabaseConnection(Main.getSettingDatabaseUsername(), - Main.getSettingDatabasePassword(), - Main.getSettingDatabaseName(), - Main.getSettingDatabaseServer()) - - val sql = "SELECT * FROM Coins WHERE SetID=$id;" - - val results = connection.runQuery(sql) - - if(results !=null) { - try { - // Find out how many rows are in the result - var size = 0 - try { - results.last() - size = results.row - results.beforeFirst() - } catch (ex: Exception) { - ex.printStackTrace() - } - - // for each coin found - for (i in 0 until size) { - results.next() - - val newCoin = Coin() - newCoin.id = results.getInt("ID") - newCoin.country = Objects.requireNonNullElse(results.getString("Country"), "US") - newCoin.name = Objects.requireNonNullElse(results.getString("Type"), "") - newCoin.year = results.getInt("Yr") - newCoin.denomination = results.getDouble("Denomination") - newCoin.value = results.getDouble("CurValue") - newCoin.mintMark = Objects.requireNonNullElse(results.getString("MintMark"), "") - newCoin.graded = results.getBoolean("Graded") - newCoin.condition = Objects.requireNonNullElse(results.getString("Grade"), "") - newCoin.error = results.getBoolean("Error") - newCoin.errorType = Objects.requireNonNullElse(results.getString("ErrorType"), "") - newCoin.note = Objects.requireNonNullElse(results.getString("Note"), "") - - if(results.getString("ObvImgExt") != null) - newCoin.obvImgExt = results.getString("ObvImgExt") - if(results.getString("RevImgExt") != null) - newCoin.revImgExt = results.getString("RevImgExt") - - addCoin(newCoin) - } - } - catch (e: SQLException) { - e.printStackTrace() - } - } - } -} \ No newline at end of file diff --git a/src/items/DatabaseConnection.kt b/src/items/DatabaseConnection.kt deleted file mode 100644 index 155a150..0000000 --- a/src/items/DatabaseConnection.kt +++ /dev/null @@ -1,124 +0,0 @@ -package items - -import java.sql.DriverManager -import java.sql.ResultSet -import java.sql.SQLException -import java.sql.Statement -import java.util.* - -class DatabaseConnection() { - - companion object { - const val SUCCESS_MESSAGE = "Success!" - const val NO_CHANGE_MESSAGE = "No changes made in database. Something probably went wrong." - const val ERROR_MESSAGE = "Error occurred while updating database. Please try again" - const val MULTIPLE_ROWS_MESSAGE = "Updated multiple rows. Make sure that this is what you wanted!" - } - - var statement: Statement? = null - - /** - * @throws SQLException if connection to SQL Database fails - * @throws ClassNotFoundException if jar file not loaded - */ - @Throws(ClassNotFoundException::class, SQLException::class) - constructor(username: String, password: String, databaseName: String = "CoinProgram", - databaseServer: String = "localhost") : this() { - - try { - // Load sql driver - Class.forName("com.mysql.jdbc.Driver") - } - catch (e: ClassNotFoundException) { - throw e - } - try { - connect(username, password, databaseName, databaseServer) - } - catch (e: SQLException) { - throw e - } - } - - /** - * @param username: Username for database access - * @param password: Password for database access - * @param databaseName: Name of database to be loaded. Defaults to "CoinProgram" - * @param databaseLocation: Name or IP address of the SQL server hosting the database. Defaults to "localhost" - * - * @throws SQLException if connection fails - * - * Connects to a database and stores the statement for later use. - */ - fun connect(username: String, password: String, databaseName: String = "CoinProgram", - databaseLocation: String = "localhost") { - - // Set username and password for connection - val connectionProps = Properties() - connectionProps["user"] = username - connectionProps["password"] = password - - val url = "jdbc:mysql://$databaseLocation:3306/" - - // Try to connect to database - val connection = try { - DriverManager.getConnection( - url, - connectionProps - ) - } - catch (e: SQLException) { - throw e - } - - // Create statement and start using database - if(connection != null) { - statement = connection.createStatement() - statement?.executeQuery("USE $databaseName;") - } - } - - /** - * @param query: SQL query to execute - * @return : Results from SQL query - * - * Runs a SQL Query and returns the results. If there was an error, returns null. - */ - fun runQuery(query: String) : ResultSet? { - return try { - statement?.executeQuery(query) - } - catch (e: SQLException) { - e.printStackTrace() - null - } - } - - /** - * @param sql: SQL command to run - * @return : number of rows affected - * - * Runs a command like INSERT, UPDATE or DELETE and returns the affected number of rows - * Returns -1 if there was an error - */ - fun runUpdate(sql: String) : Int { - return try { - val changes = statement?.executeUpdate(sql) - - return changes ?: -1 - } - catch (e: SQLException) { - e.printStackTrace() - -1 - } - } - - fun wasSuccessful(rows: Int): String { - return when(rows) { - 1 -> SUCCESS_MESSAGE - 0 -> NO_CHANGE_MESSAGE - -1 -> ERROR_MESSAGE - else -> MULTIPLE_ROWS_MESSAGE - } - } -} \ No newline at end of file diff --git a/src/res/icons/Help_icon_16.png b/src/res/icons/Help_icon_16.png new file mode 100644 index 0000000..cadced7 Binary files /dev/null and b/src/res/icons/Help_icon_16.png differ diff --git a/src/res/icons/Help_icon_24.png b/src/res/icons/Help_icon_24.png new file mode 100644 index 0000000..b260418 Binary files /dev/null and b/src/res/icons/Help_icon_24.png differ diff --git a/src/res/images/cent.png b/src/res/images/cent.png new file mode 100644 index 0000000..8867442 Binary files /dev/null and b/src/res/images/cent.png differ diff --git a/src/res/images/cent_hover.png b/src/res/images/cent_hover.png new file mode 100644 index 0000000..597e188 Binary files /dev/null and b/src/res/images/cent_hover.png differ diff --git a/src/res/images/dime.png b/src/res/images/dime.png new file mode 100644 index 0000000..1f5da4a Binary files /dev/null and b/src/res/images/dime.png differ diff --git a/src/res/images/dime_hover.png b/src/res/images/dime_hover.png new file mode 100644 index 0000000..cf9d96a Binary files /dev/null and b/src/res/images/dime_hover.png differ diff --git a/src/res/images/dollar.png b/src/res/images/dollar.png new file mode 100644 index 0000000..712342b Binary files /dev/null and b/src/res/images/dollar.png differ diff --git a/src/res/images/dollar_hover.png b/src/res/images/dollar_hover.png new file mode 100644 index 0000000..f11f951 Binary files /dev/null and b/src/res/images/dollar_hover.png differ diff --git a/src/res/images/half.png b/src/res/images/half.png new file mode 100644 index 0000000..0eac5ea Binary files /dev/null and b/src/res/images/half.png differ diff --git a/src/res/images/half_hover.png b/src/res/images/half_hover.png new file mode 100644 index 0000000..61e0b79 Binary files /dev/null and b/src/res/images/half_hover.png differ diff --git a/src/res/images/nickel.png b/src/res/images/nickel.png new file mode 100644 index 0000000..6fe74fc Binary files /dev/null and b/src/res/images/nickel.png differ diff --git a/src/res/images/nickel_hover.png b/src/res/images/nickel_hover.png new file mode 100644 index 0000000..2c2b460 Binary files /dev/null and b/src/res/images/nickel_hover.png differ diff --git a/src/res/images/quarter.png b/src/res/images/quarter.png new file mode 100644 index 0000000..eee5c41 Binary files /dev/null and b/src/res/images/quarter.png differ diff --git a/src/res/images/quarter_hover.png b/src/res/images/quarter_hover.png new file mode 100644 index 0000000..92752dd Binary files /dev/null and b/src/res/images/quarter_hover.png differ diff --git a/src/res/images/radioButton.png b/src/res/images/radioButton.png new file mode 100644 index 0000000..be74754 Binary files /dev/null and b/src/res/images/radioButton.png differ diff --git a/src/res/images/radioButton_hover.png b/src/res/images/radioButton_hover.png new file mode 100644 index 0000000..74b124e Binary files /dev/null and b/src/res/images/radioButton_hover.png differ diff --git a/src/res/images/radioButton_selected.png b/src/res/images/radioButton_selected.png new file mode 100644 index 0000000..eb85e07 Binary files /dev/null and b/src/res/images/radioButton_selected.png differ diff --git a/src/res/images/radioButton_selected_hover.png b/src/res/images/radioButton_selected_hover.png new file mode 100644 index 0000000..7ac1f28 Binary files /dev/null and b/src/res/images/radioButton_selected_hover.png differ diff --git a/src/res/strings_en_US.properties b/src/res/strings_en_US.properties new file mode 100644 index 0000000..035451e --- /dev/null +++ b/src/res/strings_en_US.properties @@ -0,0 +1,378 @@ +# DO NOT TRANSLATE------------------------------------ +app_name=Numismatist Helper +# END DO NOT TRANSLATE ------------------------------- + +# Generic strings +ok=OK +cancel=Cancel + +# Coin properties - for tables +property_coin_toString=Coin +property_coin_id=ID +property_coin_country=Country +property_coin_currency=Currency +property_coin_type=Type +property_coin_year=Year +property_coin_denomination=Denomination +property_coin_value=Value +property_coin_mintMark=Mint Mark +property_coin_graded=Graded +property_coin_grade=Condition +property_coin_error=Error +property_coin_errorType=Error Type +property_coin_inSet=In Set +property_coin_inBook=In Book +property_coin_note=Note +property_coin_container=Container +property_coin_totals=Totals: +property_coin_totals_coins=Coins: {0} +property_coin_totals_faceValue=Face Value: {0} +property_coin_totals_value=Value: {0} + +# Set Properties - for tables +property_set_toString=Set +property_set_id=ID +property_set_name=Name +property_set_year=Year +property_set_faceValue=Face Value +property_set_value=Value +property_set_note=Note +property_set_items=Items +property_set_container=Container +property_set_totals=Totals: +property_set_totals_sets=Sets: {0} +property_set_totals_items=Items: {0} +property_set_totals_faceValue=Face Value: {0} +property_set_totals_value=Value: {0} + +property_bill_toString=Banknote +property_bill_id=ID +property_bill_denomination=Denomination +property_bill_value=Value +property_bill_country=Country +property_bill_serial=Serial +property_bill_signatures=Signatures +property_bill_grade=Grade +property_bill_error=Error +property_bill_replacement=Replacement +property_bill_note=Note +property_bill_container=Container +property_bill_totals=Totals: +property_bill_totals_bills=Banknotes: {0} +property_bill_totals_faceValue=Face Value: {0} +property_bill_totals_value=Value: {0} +property_bill_totals_replacements=Replacements: {0} + +# Menu titles +menu_file=File +menu_collection=Collection +menu_settings=Settings +menu_view=View +menu_help=Help + +# File Menu items +fileMenu_home=Home +fileMenu_exit=Exit + +# Collection Menu items +collMenu_addCoin=Add Coin +collMenu_addCoinSet=Add Set +collMenu_addBill=Add Banknote +collMenu_addCoinFolder=Add Coin Folder +collMenu_addContainer=Add Container +collMenu_viewColl=View Collection + +# Settings Menu items +setMenu_itemLoc=Item Locations +setMenu_custom=Customize Country List + +customScreen_label_source=Source +customScreen_label_dest=Destination +customScreen_button_add=Add >> +customScreen_button_remove=<< Remove +customScreen_button_moveUp=\u25B2 +customScreen_button_moveDown=\u25BC + +# View Menu items +viewMenu_look=Look and Feel + +# Help Menu items +helpMenu_help=Help +helpMenu_about=About + +helpScreen_title=Help with Numismatist Helper +helpScreen_label_topics=Select a topic: +helpScreen_topic_coins=Coins +helpScreen_topic_coinFolders=Coin Folders +helpScreen_topic_bills=Banknotes +helpScreen_topic_sets=Sets +helpScreen_topic_countries=Countries +helpScreen_topic_currencies=Currencies +helpScreen_topic_containers=Containers + +customizeCountries_title=Customize Country List +customizeCountries_label_available=Available Countries +customizeCountries_label_keepOnTop=Keep On Top + +# Home Screen Items +homeScreen_label_addToColl=Add to Collection +homeScreen_label_items=Collection Items +homeScreen_label_other=Other +homeScreen_label_viewColl=View Collection +homeScreen_button_addCoin=Add Coin +homeScreen_button_addCoinSet=Add Set +homeScreen_button_addCoinFolder=Add Coin Folder +homeScreen_button_addBill=Add Banknote +homeScreen_button_addContainer=Add Container +homeScreen_button_addCountry=Add Country +homeScreen_button_addCurrency=Add Currency +homeScreen_button_viewCollection=View Spreadsheet + +# Querying database popup +databaseQueryWindow_message=Please wait while retrieving information from database.\nThis may take a while if you have a large database or a slow connection. +databaseQueryWindow_title=Querying Database + +# Item Locations Window +itemLoc_title=Item Locations +itemLoc_label_serverName=Database Server +itemLoc_label_portNumber=Port Number +itemLoc_label_DbTimeout=Connection Timeout +itemLoc_label_dbName=Database Name +itemLoc_label_dbUser=Database Username +itemLoc_label_dbPassword=Database Password +itemLoc_label_imgLoc=Images Location +itemLoc_button_defServer=Set Default +itemLoc_button_defPort=Set Default +itemLoc_button_defTimeout=Set Default +itemLoc_button_defDbName=Set Default +itemLoc_button_defDbUser=Set Default +itemLoc_button_defDbPassword=Set Default +itemLoc_button_defImgLoc=Set Default +itemLoc_button_browse=Browse +itemLoc_helpText_timeout=Timeout is in seconds. Set to 0 for no timeout +itemLoc_error_message_imagesDirectoryMissing=Images directory doesn't exist. Create it now? +itemLoc_error_title_imagesDirectoryMissing=Create Directory +itemLoc_error_message_directoryCreateFail=Failed to create directory! +itemLoc_error_title_directoryCreateFail=Error +itemLoc_error_message_databaseConnFail=Error connecting to database. Please check your settings. +itemLoc_error_title_databaseConnFail=Connection Error +itemLoc_error_message_driverMissing=SQL driver not found. Please load mysql-connector-java-5.1.149.jar into your project +itemLoc_error_title_driverMissing=SQL Driver Missing + +# Add Coin Screen Window +addCoin_title_add=Add Coin +addCoin_title_addToSet=Add Coin to {0} +addCoin_title_edit=Edit Coin +addCoin_title_editInSet=Edit Coin in {0} +addCoin_label_country=Country* +addCoin_label_year=Year* +addCoin_label_mintMark=Mint Mark +addCoin_label_currency=Currency +addCoin_label_denomination=Denomination* +addCoin_label_value=Current Value +addCoin_label_type=Coin Type +addCoin_label_container=Container +addCoin_label_grade=Grade +addCoin_label_error=Error Type +addCoin_label_note=Note +addCoin_label_obvImage=Image: Obverse +addCoin_label_revImage=Image: Reverse +addCoin_checkbox_graded=Graded +addCoin_checkbox_error=Error +addCoin_button_setObv=Set +addCoin_button_setRev=Set +addCoin_button_remObv=Remove +addCoin_button_remRev=Remove +addCoin_button_browseObv=Browse +addCoin_button_browseRev=Browse +addCoin_button_saveCopy=Save And Copy +addCoin_button_saveNew=Save And New +addCoin_message_saving=Saving coin... +addCoin_message_saved={0} saved! + +bookPage_title=Page {0} +bookPage_button_prevPage=Previous Page +bookPage_button_nextPage=Next Page + +# Add Set Screen +addSet_title_add=Add Set +addSet_title_addToSet=Add Set to {0} +addSet_title_edit=Edit {0} +addSet_label_name=Name* +addSet_label_year=Year +addSet_label_value=Current Value +addSet_label_note=Note +addSet_label_faceValue=Face Value +addSet_label_inSet=In This Set: +addSet_label_container=Container +addSet_label_obvImage=Image: Obverse +addSet_label_revImage=Image: Reverse +addSet_button_setYears=Set Years +addSet_button_addNew=Add New Item +addSet_button_addExisting=Add Existing Item +addSet_button_setObv=Set +addSet_button_setRev=Set +addSet_button_remObv=Remove +addSet_button_remRev=Remove +addSet_button_browseObv=Browse +addSet_button_browseRev=Browse +addSet_button_saveCopy=Save And Copy +addSet_button_saveNew=Save And New +addSet_menu_newCoin=Coin +addSet_menu_newBill=Banknote +addSet_menu_newSet=Set +addSet_popUp_title=Add Item to {0} +addSet_tip_inSet=Items in this set +addSet_tip_button_existingItem=Opens dialog to select an item from your collection +addSet_tip_button_addNew=Add a new item to your set +addSet_rtClickItem_edit=Edit +addSet_rtClickItem_remove=Remove from Set +addSet_message_saving=Saving Set... +addSet_message_saved={0} Saved! + +# Add Bill Screen +addBill_title_add=Add Banknote +addBill_title_addToSet=Add Banknote to {0} +addBill_title_edit=Edit Banknote +addBill_title_editInSet=Edit Banknote in {0} +addBill_label_country=Country* +addBill_label_series=Series (Year*-Letter) +addBill_label_serial=Serial Number +addBill_label_signatures=Signatures +addBill_label_currency=Currency* +addBill_label_denomination=Denomination* +addBill_label_value=Value +addBill_label_type=Bill Type +addBill_label_container=Container +addBill_label_grade=Grade +addBill_label_errorType=Error Type +addBill_label_note=Note +addBill_label_obvImage=Image: Obverse +addBill_label_revImage=Image: Reverse +addBill_checkbox_replacement=Replacement +addBill_checkbox_graded=Graded +addBill_checkbox_error=Error +addBill_button_obvSet=Set +addBill_button_revSet=Set +addBill_button_obvRemove=Remove +addBill_button_revRemove=Remove +addBill_button_obvBrowse=Browse +addBill_button_revBrowse=Browse +addBill_button_saveCopy=Save and Copy +addBill_button_saveNew=Save and New +addBill_message_saving=Saving banknote... +addBill_message_saved={0} saved! + +# Add Container Screen +addContainer_title=New Container +addContainer_label_name=Name* +addContainer_label_parent=Parent +addContainer_label_parent_description=If this container is inside of another, Parent should be the container that contains this one +addContainer_error_emptyName=Name is required + +# Book Screen +book_title={0} page {1} +book_clickToChange=Click to change label +book_rightClick_edit=Edit +book_clickMenu_newCoin=New Coin +book_clickMenu_existingCoin=Existing Coin +book_clickMenu_emptyTitle=Put coin in slot +book_clickMenu_emptyMessage=What's in this slot? +book_clickMenu_removeBook=Remove from Book +book_clickMenu_removeColl=Remove from Collection +book_clickMenu_filledTitle=Remove coin from Book +book_clickMenu_filledMessage=Would you like to remove this coin from your collection, or just take it out of this book? +book_dialog_sureMessage=Are you sure you want to remove this coin from your collection? +book_dialog_sureTitle=Remove coin from collection + +newFolder_title=New Coin Folder +newFolder_label_name=Name* +newFolder_label_numOfPages=Number of Pages* +newFolder_label_denomination=Denomination +newFolder_label_startYear=Start Year +newFolder_label_endYear=End Year +newFolder_button_preMade=Pre-made +newFolder_button_import=Import +newFolder_error_title=Invalid Input +newFolder_error_emptyTitle=Title cannot be empty. +newFolder_error_emptyPages=Number of Pages cannot be empty. +newFolder_error_startYearGreater=Start Year must be less than End Year. +newFolder_error_startYearEmpty=Must have Start Year if you have End Year. + +# New Book Page Screen +newPage_title={0} page {1} +newPage_label_row=Row +newPage_label_coinsPerRow=Coins Per Row +newPage_label_coinsPerRow2=Coins In Row 2 +newPage_label_coinSize=Coin Size +newPage_checkbox_custom=Custom Layout +newPage_checkbox_alternate=Rows Alternate Size +newPage_dropdown_cent=Cent +newPage_dropdown_nickel=Nickel +newPage_dropdown_dime=Dime +newPage_dropdown_quarter=Quarter +newPage_dropdown_half=Half Dollar +newPage_dropdown_dollar=Dollar +newPage_error_title=Invalid Input +newPage_error_invalidRows=\"Rows\" must be 1 or more +newPage_error_invalidCoins=\"Coins Per Row\" must be 1 or more +newPage_error_invalidRow2=\"Coins in Row 2\" must be 1 or more if \"Rows Alternate Size\" is checked + +preMade_title=PreMade Folders +preMade_label_folders=Folders + + +# Collection Screen +viewColl_title=Collection +viewColl_tab_coins=Coins +viewColl_tab_bills=Banknotes +viewColl_tab_sets=Sets +viewColl_rtClick_copySet=Copy +viewColl_rtClick_editSet=Edit +viewColl_rtClick_removeSet=Remove from Collection +viewColl_dialog_removeSetTitle=Remove Set +viewColl_dialog_removeSetMessage=Are you sure you want to remove this set? +viewColl_rtClick_copyCoin=Copy +viewColl_rtClick_editCoin=Edit +viewColl_rtClick_removeCoin=Remove from Collection +viewColl_dialog_removeCoinTitle=Remove Coin +viewColl_dialog_removeCoinMessage=Are you sure you want to remove this coin? +viewColl_rtClick_copyBill=Copy +viewColl_rtClick_editBill=Edit +viewColl_rtClick_removeBill=Remove from Collection +viewColl_dialog_removeBillTitle=Remove Banknote +viewColl_dialog_removeBillMessage=Are you sure you want to remove this banknote? +viewColl_button_export=Export +viewColl_error_noSelectionMessage=No item selected! please select an item or click Cancel +viewColl_error_noSelectionTitle=No Item Selected + +# About Screen Window +aboutScreen_title=About +aboutScreen_label_version=Version: {0} +aboutScreen_label_build=Build Date: {0} +aboutScreen_message=This program was designed to document and track a collection of coins, sets, and paper money.\n\ + \n\ + Designed and written by Randy Havens of Fifteen 15 Studios.\n\ + Copyright © {0} + +# Error text +error_genericTitle=Error +error_invalidDate=Invalid date! Please enter only numbers! +error_emptyCountry=Must select country. +error_emptyCurrency=Must select currency. +error_emptyYear=Year cannot be blank. +error_emptyName=Name cannot be blank. +error_seriesLetterLength=Series letter has max length of 3. +error_emptyDenomination=Denomination cannot be blank. +error_emptyValue=Value format incorrect. Must be a number or left blank. +error_fileOpen=Error opening file. +error_fileSave=Error saving file. +error_fileNotFound=File {0} not found! +error_savingFile_message=Error saving file: {0}. Please try again. +error_deletingFile_message=Error deleting file: {0}. Please try again. + +warning_file_exists_message=File already exists. Overwrite? +warning_file_write_cancelled=File not saved. Operation cancelled by user. + +export_file_saving_message=Please wait while saving file... +export_file_saving_title=Exporting Collection \ No newline at end of file diff --git a/src/res/xml/books/Whitman_Indian_Head_Cents.xml b/src/res/xml/books/Whitman_Indian_Head_Cents.xml new file mode 100644 index 0000000..60cfd74 --- /dev/null +++ b/src/res/xml/books/Whitman_Indian_Head_Cents.xml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/res/xml/countries.xml b/src/res/xml/countries.xml new file mode 100644 index 0000000..6fffd9b --- /dev/null +++ b/src/res/xml/countries.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/res/xml/countries_orig.xml b/src/res/xml/countries_orig.xml new file mode 100644 index 0000000..282a20b --- /dev/null +++ b/src/res/xml/countries_orig.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file