diff --git a/.gitignore b/.gitignore index 32858aa..ac4a441 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ -*.class +#ant specific +*/build/ +*/dist/ -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +#NetBeans specific +#ignore entire folder but make exceptions +*/nbproject/* +*/manifest.mf -# Package Files # -*.jar -*.war -*.ear +#Project specific +/ResearchManagementSystem/files/ +*/*.rms # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/ResearchManagementSystem/build.xml b/ResearchManagementSystem/build.xml new file mode 100644 index 0000000..fa499e9 --- /dev/null +++ b/ResearchManagementSystem/build.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + Builds, tests, and runs the project ResearchManagementSystem. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/build_info.properties b/ResearchManagementSystem/build_info.properties new file mode 100644 index 0000000..cb81057 --- /dev/null +++ b/ResearchManagementSystem/build_info.properties @@ -0,0 +1,4 @@ +#Mon, 23 Feb 2015 14:16:13 -0600 + +build.major.number=02 +build.minor.number=01 diff --git a/ResearchManagementSystem/deploy/02_01/ResearchManagementSystem_02_01.jar b/ResearchManagementSystem/deploy/02_01/ResearchManagementSystem_02_01.jar new file mode 100644 index 0000000..6c33f97 Binary files /dev/null and b/ResearchManagementSystem/deploy/02_01/ResearchManagementSystem_02_01.jar differ diff --git a/ResearchManagementSystem/deploy/02_01/src_02_01.zip b/ResearchManagementSystem/deploy/02_01/src_02_01.zip new file mode 100644 index 0000000..48de08c Binary files /dev/null and b/ResearchManagementSystem/deploy/02_01/src_02_01.zip differ diff --git a/ResearchManagementSystem/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar b/ResearchManagementSystem/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar new file mode 100644 index 0000000..04089d5 Binary files /dev/null and b/ResearchManagementSystem/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar differ diff --git a/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1-doc.zip b/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1-doc.zip new file mode 100644 index 0000000..75ab2cf Binary files /dev/null and b/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1-doc.zip differ diff --git a/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1.jar b/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1.jar new file mode 100644 index 0000000..7f26dc3 Binary files /dev/null and b/ResearchManagementSystem/lib/beans-binding/beansbinding-1.2.1.jar differ diff --git a/ResearchManagementSystem/lib/jdatepicker-1.3.4.jar b/ResearchManagementSystem/lib/jdatepicker-1.3.4.jar new file mode 100644 index 0000000..a716259 Binary files /dev/null and b/ResearchManagementSystem/lib/jdatepicker-1.3.4.jar differ diff --git a/ResearchManagementSystem/lib/nblibraries.properties b/ResearchManagementSystem/lib/nblibraries.properties new file mode 100644 index 0000000..65696c1 --- /dev/null +++ b/ResearchManagementSystem/lib/nblibraries.properties @@ -0,0 +1,10 @@ +libs.beans-binding.classpath=\ + ${base}/beans-binding/beansbinding-1.2.1.jar +libs.beans-binding.displayName=Beans Binding +libs.beans-binding.javadoc=\ + ${base}/beans-binding/beansbinding-1.2.1-doc.zip +libs.beans-binding.prop-maven-dependencies=org.jdesktop:beansbinding:1.2.1:jar +libs.CopyLibs.classpath=\ + ${base}/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar +libs.CopyLibs.displayName=CopyLibs Task +libs.CopyLibs.prop-version=2.0 diff --git a/ResearchManagementSystem/lib/swingx-all-1.6.4.jar b/ResearchManagementSystem/lib/swingx-all-1.6.4.jar new file mode 100644 index 0000000..008528c Binary files /dev/null and b/ResearchManagementSystem/lib/swingx-all-1.6.4.jar differ diff --git a/ResearchManagementSystem/src/rms/control/Loader.java b/ResearchManagementSystem/src/rms/control/Loader.java new file mode 100644 index 0000000..035b286 --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/Loader.java @@ -0,0 +1,89 @@ +package rms.control; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import rms.model.State; +import rms.util.Helpers; + +/** + * Facilities to load the State from file and store it to file + * + * @author Timothy + */ +public class Loader { + + private static final boolean DUBUG_LOADING = false; + private static final Logger thisLog = Logger.getLogger(Loader.class.getName()); + private static final String STATE_FILE_NAME = String.format("index_%s.rms", Loader.class.getPackage().getSpecificationVersion()); + + private static String getRootDir(){ + return System.getProperty("user.dir"); + } + + /** + * Attempts to deserialize the stored {@code State} from file + * + * @return the {@code State} object stored in file or null if unable to deserialize + * + * @see rms.model.State + */ + public static synchronized State loadFromFile(){ + if(DUBUG_LOADING)System.out.println("Loader.loadFromFile()"); + + String baseDir = getRootDir(); + if(baseDir == null) { + thisLog.log(Level.SEVERE, "Unable to determine user directory."); + return null; + } + + State retVal; + ObjectInputStream in = null; + try { + in = new ObjectInputStream(new FileInputStream(baseDir.concat("/").concat(STATE_FILE_NAME))); + retVal = (State) in.readObject(); + } catch (IOException | ClassNotFoundException ex) { + thisLog.log(Level.INFO, "Unable to load state. Creating new.", ex); + retVal = new State(); + } finally { + Helpers.closeResource(in); + } + + return retVal; + } + + /** + * Attempts to serialize the given {@code State} object to file + * + * @param state {@code State} object to serialize + * + * @return true iff serialization was successful + * + * @see rms.model.State + */ + public static synchronized boolean storeToFile(State state){ + if(DUBUG_LOADING)System.out.println("Loader.storeToFile()"); + String baseDir = getRootDir(); + if(baseDir == null) { + thisLog.log(Level.SEVERE, "Unable to determine user directory."); + return false; + } + + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(new FileOutputStream(baseDir.concat("/").concat(STATE_FILE_NAME))); + out.writeObject(state); + } catch (IOException ex) { + thisLog.log(Level.SEVERE, "Unable to store state.", ex); + return false; + } finally { + Helpers.closeResource(out); + } + + return true; + } +} diff --git a/ResearchManagementSystem/src/rms/control/Main.java b/ResearchManagementSystem/src/rms/control/Main.java new file mode 100644 index 0000000..e195dfc --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/Main.java @@ -0,0 +1,127 @@ +package rms.control; + +import java.io.File; +import java.util.logging.Logger; +import javax.swing.JFileChooser; +import javax.swing.UIManager; +import rms.model.State; +import rms.model.item.ItemFactory; +import rms.model.item.ItemThread; +import rms.view.MainFrame; + +/** + * + * @author Timothy + */ +public class Main { + + private static final Logger thisLog = Logger.getLogger(Main.class.getName()); + + private static MainFrame gui; + private static State state; + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + // + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) { + thisLog.log(java.util.logging.Level.SEVERE, "Error setting L&F", ex); + } + // + + //create the view + gui = MainFrame.instance(); + + // display the UI + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + gui.setVisible(true); + gui.loadStateAndPopulate(); + } + }); + } + + /** + * Load the data from file to State + * @return true iff loading was successful + */ + public static boolean loadStateFromFile(){ + state = Loader.loadFromFile(); + return state != null; + } + + /** + * Store the current state to file + * @return + */ + public static boolean storeStateToFile(){ + return Loader.storeToFile(state); + } + + public static void deleteThread(){ + state.getThreads().remove(gui.getSelectedThread()); + gui.refreshThreadListAndDisplay(); + gui.clearSelectedThread(); + } + + /** + * Create a new task on the current thread. If there is no current thread, + * create one. + */ + public static void newTask() { + ItemThread td = getSelectedThreadOrCreate(); + ItemFactory.newTaskItem(td); + gui.refreshSelectedThread(); + } + + /** + * Create a new file on the current thread. If there is no current thread, + * create one. + */ + public static void newFile() { + ItemThread td = getSelectedThreadOrCreate(); + JFileChooser chooser = new JFileChooser(); + chooser.setMultiSelectionEnabled(true); + if(chooser.showOpenDialog(gui) == JFileChooser.APPROVE_OPTION) { + for(File f : chooser.getSelectedFiles()){ + ItemFactory.newFileItem(td, f); + } + } + gui.refreshSelectedThread(); + } + + /** + * Create a new note on the current thread. If there is no current thread, + * create one. + */ + public static void newNote() { + ItemThread td = getSelectedThreadOrCreate(); + ItemFactory.newNoteItem(td); + gui.refreshSelectedThread(); + } + + public static State getState(){ + return state; + } + + /** + * Gets the currently selected thread. If none is selected, then creates new. + * @return + */ + private static ItemThread getSelectedThreadOrCreate(){ + ItemThread td = gui.getSelectedThread(); + if(td == null){ + td = gui.createAndShowNewThread(); + } + return td; + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/AbstractFinder.java b/ResearchManagementSystem/src/rms/control/search/AbstractFinder.java new file mode 100644 index 0000000..12fb9ee --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/AbstractFinder.java @@ -0,0 +1,56 @@ +package rms.control.search; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import rms.model.item.Item; +import rms.model.item.ItemThread; + +/** + * An AbstractFinder takes a Collection of {@link ItemThread} and returns the + * subset which matches the criteria defined for the specific implementation. + * + * @author Timothy + */ +public abstract class AbstractFinder { + + /** + * Searches the {@code input} Collection for {@link ItemThread}s which match + * the criteria of the subclass implementation. + * + * @param input + * @return Set of {@link ItemThread}s which match the criteria + */ + public Set find(Collection input){ + HashSet retVal = new HashSet<>(); + for(ItemThread t : input){ + if(accept(t)){ + retVal.add(t); + continue; //continue with the next thread + } + + for(Item i : t){ + if(accept(i)){ + retVal.add(t); + break; //skip remaining items in thread + } + } + } + return retVal; + } + + /** + * + * @param item + * @return {@code true} iff the given {@link Item} should be returned by this AbstractFinder + */ + protected abstract boolean accept(Item item); + + /** + * + * @param thread + * @return {@code true} iff the given {@link ItemThread} should be returned by this AbstractFinder + */ + protected abstract boolean accept(ItemThread thread); + +} diff --git a/ResearchManagementSystem/src/rms/control/search/DateRangeFinder.java b/ResearchManagementSystem/src/rms/control/search/DateRangeFinder.java new file mode 100644 index 0000000..c44a2de --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/DateRangeFinder.java @@ -0,0 +1,65 @@ +package rms.control.search; + +import java.util.Date; +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.TaskItem; +import rms.util.DateHelpers; +import rms.util.DateRangeType; + +/** + * Finds tasks whose creation/modification time or deadline is within the + * given date range. + * + * @author Timothy + */ +public class DateRangeFinder extends AbstractFinder { + + private final DateRangeType type; + private final Date start; + private final Date end; + + /** + * + * @param type the type of timestamp to search + * @param start inclusive start date of the range + * @param end inclusive end date of the range + */ + public DateRangeFinder(DateRangeType type, Date start, Date end){ + this.type = type; + this.start = DateHelpers.removeTime(start); + this.end = DateHelpers.removeTime(end); + } + + @Override + protected boolean accept(Item i) { + Date itemDate = null; + + switch(type){ + case CREATED: + itemDate = i.getCreationTime(); + break; + case MODIFIED: + itemDate = i.getModificationTime(); + break; + case DEADLINE: + if(i instanceof TaskItem){ + itemDate = ((TaskItem)i).getDeadline(); + } + break; + default: + //TODO + } + + if(itemDate == null) return false; + + itemDate = DateHelpers.removeTime(itemDate); + return DateHelpers.afterEqual(itemDate, start) && DateHelpers.beforeEqual(itemDate, end); + } + + @Override + protected boolean accept(ItemThread t) { + //NOTE: I could implement this one to shortcut the search + return false; + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/LateTaskFinder.java b/ResearchManagementSystem/src/rms/control/search/LateTaskFinder.java new file mode 100644 index 0000000..3131b22 --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/LateTaskFinder.java @@ -0,0 +1,27 @@ +package rms.control.search; + +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.TaskItem; + +/** + * Finds {@link TaskItem}s which are overdue, i.e. their deadline is in the past. + * + * @author Timothy + */ +public class LateTaskFinder extends AbstractFinder { + + @Override + protected boolean accept(Item i) { + if(i instanceof TaskItem){ + TaskItem ti = (TaskItem)i; + return ti.isOverdue(); + } + return false; + } + + @Override + protected boolean accept(ItemThread t) { + return false; + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/PendingTaskFinder.java b/ResearchManagementSystem/src/rms/control/search/PendingTaskFinder.java new file mode 100644 index 0000000..35a0834 --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/PendingTaskFinder.java @@ -0,0 +1,27 @@ +package rms.control.search; + +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.TaskItem; + +/** + * Finds {@link TaskItem}s which have not been marked completed + * + * @author Timothy + */ +public class PendingTaskFinder extends AbstractFinder { + + @Override + protected boolean accept(Item i) { + if(i instanceof TaskItem){ + TaskItem ti = (TaskItem)i; + return !ti.isComplete(); + } + return false; + } + + @Override + protected boolean accept(ItemThread t) { + return false; + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/TagFinder.java b/ResearchManagementSystem/src/rms/control/search/TagFinder.java new file mode 100644 index 0000000..171f517 --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/TagFinder.java @@ -0,0 +1,38 @@ +package rms.control.search; + +import rms.model.Tag; +import rms.model.item.Item; +import rms.model.item.ItemThread; + +/** + * Finds {@link ItemThread}s with the given {@link Tag} + * + * @author Timothy + */ +public class TagFinder extends AbstractFinder { + + private final Tag tag; + + /** + * + * @param tag {@link Tag} to search {@link ItemThread}s for + */ + public TagFinder(Tag tag){ + this.tag = tag; + } + + /** + * There is no {@link Tag} associated with an {@link Item} + * @param i + * @return {@code false} + */ + @Override + protected boolean accept(Item i) { + return false; + } + + @Override + protected boolean accept(ItemThread t) { + return t.getTags().contains(tag); + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/TextFinder.java b/ResearchManagementSystem/src/rms/control/search/TextFinder.java new file mode 100644 index 0000000..f66e04e --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/TextFinder.java @@ -0,0 +1,53 @@ +package rms.control.search; + +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.TextItem; +import rms.util.Helpers; + +/** + * Finds {@link TextItem}s with text containing the given string + * + * @author Timothy + */ +public class TextFinder extends AbstractFinder { + + private final String searchText; + + public TextFinder(String searchText){ + this.searchText = searchText; + } + + + /** + * + * @param i + * @return {@code true} iff the name of the {@code Item} or its body text + * contains (case insensitive) the search text + */ + @Override + protected boolean accept(Item i) { + //check the item name for the text + if(Helpers.containsIgnoreCase(i.getName(), searchText)) return true; + + //if the item is a TextItem, check the text + if(i instanceof TextItem){ + TextItem ti = (TextItem)i; + if(ti.getText() == null) return false; + + return Helpers.containsIgnoreCase(ti.getText(), searchText); + } + return false; + } + + /** + * + * @param t + * @return {@code true} iff the name of the {@code ItemThread} contains + * (case insensitive) the search text + */ + @Override + protected boolean accept(ItemThread t) { + return Helpers.containsIgnoreCase(t.getName(), searchText); + } +} diff --git a/ResearchManagementSystem/src/rms/control/search/UpcomingTaskFinder.java b/ResearchManagementSystem/src/rms/control/search/UpcomingTaskFinder.java new file mode 100644 index 0000000..7f5b9f6 --- /dev/null +++ b/ResearchManagementSystem/src/rms/control/search/UpcomingTaskFinder.java @@ -0,0 +1,78 @@ +package rms.control.search; + +import java.util.Date; +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.TaskItem; +import rms.util.DateHelpers; +import rms.util.DateUnit; + +/** + * Finds tasks whose deadline is within the next X time units + * + * @author Timothy + */ +public class UpcomingTaskFinder extends AbstractFinder { + + private final int increment; + private final DateUnit unit; + + /** + * @param increment number of {@code DateUnit}s into the future to search + * @param unit unit type associated with the increment + */ + public UpcomingTaskFinder(int increment, DateUnit unit){ + this.increment = increment; + this.unit = unit; + } + + /** + * + * @param i {@link Item} to test for acceptance + * @return {@code true} iff the deadline on {@code i} is within the range + * specified by this instance + */ + @Override + protected boolean accept(Item i) { + if(i instanceof TaskItem){ + TaskItem ti = (TaskItem)i; + Date deadline = ti.getDeadline(); + + if(deadline == null) return false; + + Date today = DateHelpers.Today(); + if(deadline.before(today)) return false; + + Date finalDate = null; + switch(unit){ + case DAY: + finalDate = DateHelpers.addDays(today, increment); + break; + case WEEK: + finalDate = DateHelpers.addWeeks(today, increment); + break; + case MONTH: + finalDate = DateHelpers.addMonths(today, increment); + break; + default: + //TODO + } + + if(finalDate == null) return false; + + return DateHelpers.beforeEqual(deadline, finalDate); + } + return false; + } + + /** + * Task deadlines are not directly associated with the {@link ItemThread} + * + * @param t [ignored] + * @return {@code false} + */ + @Override + protected boolean accept(ItemThread t) { + return false; + } +} diff --git a/ResearchManagementSystem/src/rms/model/State.java b/ResearchManagementSystem/src/rms/model/State.java new file mode 100644 index 0000000..0c35449 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/State.java @@ -0,0 +1,61 @@ +package rms.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import rms.model.item.ItemThread; + +/** + * Stores references to all items + * + * @author Timothy + */ +public class State implements Serializable { + + private static final long serialVersionUID = 01L; + + private int nextItemNumber; + private int nextThreadNumber; + private final Set threads; + private final Set tags; + + public State(){ + nextItemNumber = 0; + nextThreadNumber = 0; + threads = new HashSet<>(); + tags = new HashSet<>(); + } + + public int getNextItemNumber(){ + return ++nextItemNumber; + } + + public int getNextThreadNumber(){ + return ++nextThreadNumber; + } + + public ItemThread createNewThread(){ + ItemThread t = new ItemThread(); + this.threads.add(t); + return t; + } + + public Set getThreads(){ + return threads; + } + + public Set getTags(){ + return tags; + } + + public Tag newTag(String name){ + Tag t = new Tag(name); + tags.add(t); + return t; + } + + //TODO: + // 1. keep a list of tags? + // 2. keep a mapping of tag to item? + // 3. keep a mapping of mod time to item? +} diff --git a/ResearchManagementSystem/src/rms/model/Tag.java b/ResearchManagementSystem/src/rms/model/Tag.java new file mode 100644 index 0000000..159bdb0 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/Tag.java @@ -0,0 +1,46 @@ +package rms.model; + +import java.io.Serializable; +import java.util.Objects; + +/** + * + * @author Timothy + */ +public class Tag implements Serializable, Comparable { + + private static final long serialVersionUID = 01L; + + private final String name; + + protected Tag(String name){ + this.name = name; + } + + @Override + public String toString(){ + return name; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.name); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Tag other = (Tag) obj; + return Objects.equals(this.name, other.name); + } + + @Override + public int compareTo(Tag o) { + return this.name.compareTo(o.name); + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/FileItem.java b/ResearchManagementSystem/src/rms/model/item/FileItem.java new file mode 100644 index 0000000..bcc6477 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/FileItem.java @@ -0,0 +1,46 @@ +package rms.model.item; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.file.Files; +import static java.nio.file.StandardCopyOption.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Timothy + */ +public class FileItem extends Item implements Serializable { + + private static final long serialVersionUID = 01L; + private static final Logger thisLog = Logger.getLogger(FileItem.class.getName()); + + + //TODO: do we care about if and when the file content itself was modified?? + + private final File localFile; + + protected FileItem(ItemThread parentThread, File sourceFile){ + super(parentThread); + + localFile = new File(parentThread.getDataFolder(), sourceFile.getName()); + + try { + Files.copy(sourceFile.toPath(), localFile.toPath(), COPY_ATTRIBUTES, REPLACE_EXISTING); + } catch (IOException ex) { + thisLog.log(Level.SEVERE, "Unable to copy file", ex); + } + + } + + public File getFile(){ + return localFile; + } + + @Override + public String getItemTypeName() { + return "File"; + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/Item.java b/ResearchManagementSystem/src/rms/model/item/Item.java new file mode 100644 index 0000000..8bedb5a --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/Item.java @@ -0,0 +1,81 @@ +package rms.model.item; + +import java.io.Serializable; +import java.util.Date; +import rms.control.Main; + +/** + * + * @author Timothy + */ +public abstract class Item implements Serializable { + + private static final long serialVersionUID = 01L; + + protected final int itemID; + protected final ItemThread parentThread; + protected final Date created; + protected Date modified; + protected String name; + + protected Item(ItemThread parentThread, String name){ + if(parentThread == null) throw new IllegalArgumentException("Parent thread cannot be null"); + + this.itemID = Main.getState().getNextItemNumber(); + this.parentThread = parentThread; + this.created = new Date(); + this.modified = (Date)created.clone(); + this.name = name; + } + + protected Item(ItemThread parentThread){ + this(parentThread, ""); + } + + public int getID(){ + return itemID; + } + + public ItemThread getThread(){ + return parentThread; + } + + public Item getParent(){ + int pIndex = parentThread.indexOf(this) - 1; + if(pIndex < 0){ + return null; + } + return parentThread.get(pIndex); + } + + public Item getChild(){ + int cIndex = parentThread.indexOf(this) + 1; + if(cIndex >= parentThread.size()){ + return null; + } + return parentThread.get(cIndex); + } + + public Date getCreationTime(){ + return created; + } + + public Date getModificationTime(){ + return modified; + } + + public String getName(){ + return name; + } + + public void setName(String newName){ + this.name = newName; + } + + public void touch(){ + this.modified = new Date(); + this.getThread().touch(); + } + + public abstract String getItemTypeName(); +} diff --git a/ResearchManagementSystem/src/rms/model/item/ItemFactory.java b/ResearchManagementSystem/src/rms/model/item/ItemFactory.java new file mode 100644 index 0000000..a8af967 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/ItemFactory.java @@ -0,0 +1,40 @@ +package rms.model.item; + +import java.io.File; + +/** + * + * @author Timothy + */ +public class ItemFactory { + + public static NoteItem newNoteItem(ItemThread parentThread) { + NoteItem i = new NoteItem(parentThread); + parentThread.add(i); + return i; + } + + public static NoteItem newNoteItem(ItemThread parentThread, String text) { + NoteItem i = new NoteItem(parentThread, text); + parentThread.add(i); + return i; + } + + public static TaskItem newTaskItem(ItemThread parentThread) { + TaskItem i = new TaskItem(parentThread); + parentThread.add(i); + return i; + } + + public static TaskItem newTaskItem(ItemThread parentThread, String text) { + TaskItem i = new TaskItem(parentThread, text); + parentThread.add(i); + return i; + } + + public static FileItem newFileItem(ItemThread parentThread, File srcFile) { + FileItem i = new FileItem(parentThread, srcFile); + parentThread.add(i); + return i; + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/ItemThread.java b/ResearchManagementSystem/src/rms/model/item/ItemThread.java new file mode 100644 index 0000000..a5a8c39 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/ItemThread.java @@ -0,0 +1,110 @@ +package rms.model.item; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import rms.model.Tag; +import rms.control.Main; + +/** + * + * @author Timothy + */ +public class ItemThread implements Iterable, Serializable, Comparable { + + private static final long serialVersionUID = 01L; + + private final ArrayList data; + private final int threadID; + private File dataFolder; + protected Date modified; + protected final Set tags; + protected String name; + + public ItemThread(){ + this(null); + } + + public ItemThread(String name){ + this.data = new ArrayList<>(); + this.threadID = Main.getState().getNextThreadNumber(); + this.dataFolder = null; + this.tags = new HashSet<>(); + setName(name); + touch(); + } + + protected void add(Item i){ + data.add(i); + touch(); + } + + public int indexOf(Item i){ + return data.indexOf(i); + } + + public Item get(int index){ + return data.get(index); + } + + public void remove(Item i){ + data.remove(i); + touch(); + } + + public int size(){ + return data.size(); + } + + public File getDataFolder(){ + if(dataFolder == null){ + File root = new File("files"); + dataFolder = new File(root, String.format("%09d", this.threadID)); + dataFolder.mkdirs(); + } + + return dataFolder; + } + + public final void touch(){ + this.modified = new Date(); + } + + public Date getModificationTime(){ + return modified; + } + + public Set getTags(){ + return tags; + } + + public String getName(){ + return name; + } + + public final void setName(String newName){ + if(newName == null || newName.isEmpty()){ + newName = ""; + } + name = newName; + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + + @Override + public String toString(){ + return String.format("%s [%2$tm/%2$te/%2$ty %2$tR]", name, modified); + } + + @Override + public int compareTo(ItemThread other) { + return other.getModificationTime().compareTo(this.getModificationTime()); + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/NoteItem.java b/ResearchManagementSystem/src/rms/model/item/NoteItem.java new file mode 100644 index 0000000..ee98748 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/NoteItem.java @@ -0,0 +1,25 @@ +package rms.model.item; + +import java.io.Serializable; + +/** + * + * @author Timothy + */ +public class NoteItem extends TextItem implements Serializable { + + private static final long serialVersionUID = 01L; + + protected NoteItem(ItemThread parentThread){ + this(parentThread, null); + } + + protected NoteItem(ItemThread parentThread, String text){ + super(parentThread, text); + } + + @Override + public String getItemTypeName() { + return "Note"; + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/TaskItem.java b/ResearchManagementSystem/src/rms/model/item/TaskItem.java new file mode 100644 index 0000000..d954347 --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/TaskItem.java @@ -0,0 +1,58 @@ +package rms.model.item; + +import java.io.Serializable; +import java.util.Date; +import rms.util.DateHelpers; + +/** + * + * @author Timothy + */ +public class TaskItem extends TextItem implements Serializable { + + private static final long serialVersionUID = 01L; + + public static enum StatusIndicator {PENDING, COMPLETE, OVERDUE}; + + private Date deadline; + private boolean completed; + + protected TaskItem(ItemThread parentThread){ + this(parentThread, null); + } + + protected TaskItem(ItemThread parentThread, String text){ + super(parentThread, text); + this.completed = false; + } + public void setDeadline(Date newDeadline){ + this.deadline = newDeadline; + } + + public Date getDeadline(){ + return deadline; + } + + public boolean isComplete(){ + return completed; + } + + public boolean isOverdue(){ + return !completed && deadline != null && deadline.before(DateHelpers.Today()); + } + + public void markCompleted(){ + touch(); + completed = true; + } + + public void markActive(){ + touch(); + completed = false; + } + + @Override + public String getItemTypeName() { + return "Task"; + } +} diff --git a/ResearchManagementSystem/src/rms/model/item/TextItem.java b/ResearchManagementSystem/src/rms/model/item/TextItem.java new file mode 100644 index 0000000..bc517ea --- /dev/null +++ b/ResearchManagementSystem/src/rms/model/item/TextItem.java @@ -0,0 +1,34 @@ +package rms.model.item; + +/** + * + * @author Timothy + */ +public abstract class TextItem extends Item{ + + private static final long serialVersionUID = 01L; + + private String text; + + public TextItem(ItemThread parentThread, String text) { + super(parentThread); + this.text = text; + } + + public String getText(){ + return text; + } + + public void replaceText(String newText){ + touch(); + text = newText; + } + + public void appendText(String newText){ + touch(); + text = text.concat("\n").concat(newText); + } + + @Override + public abstract String getItemTypeName(); +} diff --git a/ResearchManagementSystem/src/rms/util/DateHelpers.java b/ResearchManagementSystem/src/rms/util/DateHelpers.java new file mode 100644 index 0000000..29198e0 --- /dev/null +++ b/ResearchManagementSystem/src/rms/util/DateHelpers.java @@ -0,0 +1,109 @@ +package rms.util; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * Static utility methods for Date objects + * @author Timothy + */ +public final class DateHelpers { + + /** + * @return {@link Date} object representing midnight of the current day + */ + public static Date Today(){ + return removeTime(new Date()); + } + + /** + * @return {@link Date} object representing the current date and time + */ + public static Date Now(){ + Calendar inst = GregorianCalendar.getInstance(); + return inst.getTime(); + } + + /** + * Sets the time on the given {@link Date} object to midnight + * @param d + * @return + */ + public static Date removeTime(Date d){ + Calendar inst = GregorianCalendar.getInstance(); + inst.setTime(d); + inst.set(Calendar.HOUR_OF_DAY, 0); + inst.set(Calendar.MINUTE, 0); + inst.set(Calendar.SECOND, 0); + inst.set(Calendar.MILLISECOND, 0); + return inst.getTime(); + } + + /** + * Determines the number of days between two dates + * @param d1 + * @param d2 + * @return + */ + public static int dayDiff(Date d1, Date d2){ + Date d1a = removeTime(d1); + Date d2a = removeTime(d2); + + long timeDiff = d1a.getTime() - d2a.getTime(); + + // milliseconds in 1 day = 86400000 + return (int)(timeDiff / 86400000); + } + + public static Date addDays(Date startDate, int numDays){ + Calendar inst = GregorianCalendar.getInstance(); + inst.setTime(startDate); + inst.add(Calendar.DAY_OF_YEAR, numDays); + return inst.getTime(); + } + + public static Date addWeeks(Date startDate, int numWeeks){ + Calendar inst = GregorianCalendar.getInstance(); + inst.setTime(startDate); + inst.add(Calendar.WEEK_OF_YEAR, numWeeks); + return inst.getTime(); + } + + public static Date addMonths(Date startDate, int numMonths){ + Calendar inst = GregorianCalendar.getInstance(); + inst.setTime(startDate); + inst.add(Calendar.MONTH, numMonths); + return inst.getTime(); + } + + /** + * Tests if the first date is before or equal to the second date. + * + * @param one + * @param two + * @return true if and only if the instant of time + * represented by the first Date object is equal to + * or earlier than the instant represented by the second; + * false otherwise. + * @exception NullPointerException if either is null. + */ + public static boolean beforeEqual(Date one, Date two) { + return one.getTime() <= two.getTime(); + } + + /** + * Tests if the first date is after or equal to the second date. + * + * @param one + * @param two + * @return true if and only if the instant represented + * by the first Date object is equal to or later + * than the instant represented by the second; + * false otherwise. + * @exception NullPointerException if either is null. + */ + public static boolean afterEqual(Date one, Date two) { + return one.getTime() >= two.getTime(); + } +} diff --git a/ResearchManagementSystem/src/rms/util/DateRangeType.java b/ResearchManagementSystem/src/rms/util/DateRangeType.java new file mode 100644 index 0000000..8fbf141 --- /dev/null +++ b/ResearchManagementSystem/src/rms/util/DateRangeType.java @@ -0,0 +1,23 @@ +package rms.util; + +/** + * + * @author Timothy + */ +public enum DateRangeType { + CREATED("completed"), MODIFIED("modified"), DEADLINE("with deadline"); + + private final String text; + + /** + * @param text + */ + private DateRangeType(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } +} diff --git a/ResearchManagementSystem/src/rms/util/DateUnit.java b/ResearchManagementSystem/src/rms/util/DateUnit.java new file mode 100644 index 0000000..85b644f --- /dev/null +++ b/ResearchManagementSystem/src/rms/util/DateUnit.java @@ -0,0 +1,23 @@ +package rms.util; + +/** + * + * @author Timothy + */ +public enum DateUnit { + DAY("day(s)"), WEEK("week(s)"), MONTH("month(s)"); + + private final String text; + + /** + * @param text + */ + private DateUnit(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } +} diff --git a/ResearchManagementSystem/src/rms/util/Helpers.java b/ResearchManagementSystem/src/rms/util/Helpers.java new file mode 100644 index 0000000..ae9175d --- /dev/null +++ b/ResearchManagementSystem/src/rms/util/Helpers.java @@ -0,0 +1,42 @@ +package rms.util; + +import java.io.Closeable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Static utility methods + * + * @author Timothy + */ +public final class Helpers { + + private static final Logger thisLog = Logger.getLogger(Helpers.class.getName()); + + /** + * Checks if String s1 contains string s2 in a case insensitive manner + * + * @param s1 + * @param s2 + * @return + */ + public static boolean containsIgnoreCase(String s1, String s2){ + return s1.toLowerCase().contains(s2.toLowerCase()); + } + + /** + * Closes the Closable resource if it is not null or already closed. + * + * @param c + */ + public static void closeResource(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException ex) { + thisLog.log(Level.WARNING, "Error closing resource", ex); + } + } + } +} diff --git a/ResearchManagementSystem/src/rms/view/MainFrame.form b/ResearchManagementSystem/src/rms/view/MainFrame.form new file mode 100644 index 0000000..69ce9f3 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/MainFrame.form @@ -0,0 +1,540 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/src/rms/view/MainFrame.java b/ResearchManagementSystem/src/rms/view/MainFrame.java new file mode 100644 index 0000000..0ed392f --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/MainFrame.java @@ -0,0 +1,1055 @@ +package rms.view; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.concurrent.ExecutionException; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollBar; +import javax.swing.SwingWorker; +import javax.swing.border.EtchedBorder; +import rms.control.Main; +import rms.control.search.AbstractFinder; +import rms.control.search.LateTaskFinder; +import rms.control.search.PendingTaskFinder; +import rms.control.search.TagFinder; +import rms.model.Tag; +import rms.model.item.FileItem; +import rms.model.item.Item; +import rms.model.item.ItemThread; +import rms.model.item.NoteItem; +import rms.model.item.TaskItem; +import rms.view.item.PanelFileItem; +import rms.view.item.PanelNoteItem; +import rms.view.item.PanelTaskItem; +import rms.view.search.BaseDialog; +import rms.view.search.DialogDateRange; +import rms.view.search.DialogDeadline; +import rms.view.search.DialogSearchTags; +import rms.view.search.DialogSearchText; +import rms.view.util.LoadingPanel; +import rms.view.util.Prompts; +import rms.view.util.Prompts.PromptType; +import rms.view.util.WrapLayout; + +/** + * The main UI window (singleton) + * + * @author Timothy + */ +public class MainFrame extends rms.view.util.NotificationFrame { + + private final boolean DEBUG_WORKERS = false; + + private static MainFrame inst = null; + + /** + * Creates new form MainFrame2 + */ + private MainFrame() { + initComponents(); + initComponentsMore(); + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanelEmptyThread = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + jButtonAddTag = new javax.swing.JButton(); + jPopupMenuTag = new javax.swing.JPopupMenu(); + jMenuItemDeleteTag = new javax.swing.JMenuItem(); + jMenuNewItem = new javax.swing.JMenu(); + jMenuItemNewTask = new javax.swing.JMenuItem(); + jMenuItemNewNote = new javax.swing.JMenuItem(); + jMenuItemNewFile = new javax.swing.JMenuItem(); + jSplitPaneHoriz = new javax.swing.JSplitPane(); + jScrollPaneThreadList = new javax.swing.JScrollPane(); + jListThreads = new javax.swing.JList(); + jPanelThreadInfo = new javax.swing.JPanel(); + jSplitPaneVert = new javax.swing.JSplitPane(); + jScrollPaneTags = new javax.swing.JScrollPane(); + jPanelTags = new javax.swing.JPanel(); + jPanelThreadContent = new javax.swing.JPanel(); + jScrollPaneContent = new javax.swing.JScrollPane(); + jXPanelContent = new org.jdesktop.swingx.JXPanel(); + jPanelHeader = new javax.swing.JPanel(); + jButtonDeleteThread = new javax.swing.JButton(); + jButtoNewFile = new javax.swing.JButton(); + jButtonNewTask = new javax.swing.JButton(); + jButtonNewNote = new javax.swing.JButton(); + jLabelThreadName = new javax.swing.JLabel(); + jMenuBar = new javax.swing.JMenuBar(); + jMenuNew = new javax.swing.JMenu(); + jMenuItemNewThread = new javax.swing.JMenuItem(); + jMenuData = new javax.swing.JMenu(); + jMenuItemSave = new javax.swing.JMenuItem(); + jMenuItemReload = new javax.swing.JMenuItem(); + jMenuFind = new javax.swing.JMenu(); + jMenuItemShowAll = new javax.swing.JMenuItem(); + jSeparator2 = new javax.swing.JPopupMenu.Separator(); + jMenuItemFindTag = new javax.swing.JMenuItem(); + jMenuItemFindDate = new javax.swing.JMenuItem(); + jMenuItemFindText = new javax.swing.JMenuItem(); + jSeparator1 = new javax.swing.JPopupMenu.Separator(); + jMenuItemFindDeadline = new javax.swing.JMenuItem(); + jMenuItemFindPending = new javax.swing.JMenuItem(); + jMenuItemFindLateTasks = new javax.swing.JMenuItem(); + jMenuHelp = new javax.swing.JMenu(); + jMenuItemAbout = new javax.swing.JMenuItem(); + + jPanelEmptyThread.setPreferredSize(new java.awt.Dimension(345, 95)); + + jLabel1.setText("This thread is currently empty."); + + jLabel2.setText("You may add items using the New menu or the buttons above."); + + jLabel3.setText("You can also use the Delete button to delete this thread."); + + javax.swing.GroupLayout jPanelEmptyThreadLayout = new javax.swing.GroupLayout(jPanelEmptyThread); + jPanelEmptyThread.setLayout(jPanelEmptyThreadLayout); + jPanelEmptyThreadLayout.setHorizontalGroup( + jPanelEmptyThreadLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelEmptyThreadLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelEmptyThreadLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(jLabel2) + .addComponent(jLabel3)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanelEmptyThreadLayout.setVerticalGroup( + jPanelEmptyThreadLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelEmptyThreadLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3) + .addContainerGap(52, Short.MAX_VALUE)) + ); + + jButtonAddTag.setBackground(javax.swing.UIManager.getDefaults().getColor("Button.shadow")); + jButtonAddTag.setText("+"); + jButtonAddTag.setBorder(javax.swing.BorderFactory.createCompoundBorder(javax.swing.BorderFactory.createEtchedBorder(), javax.swing.BorderFactory.createEmptyBorder(3, 5, 3, 5))); + jButtonAddTag.setBorderPainted(false); + jButtonAddTag.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + jButtonAddTag.setMargin(new java.awt.Insets(2, 3, 2, 3)); + jButtonAddTag.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonAddTagActionPerformed(evt); + } + }); + + jMenuItemDeleteTag.setText("Delete"); + jMenuItemDeleteTag.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemDeleteTagActionPerformed(evt); + } + }); + jPopupMenuTag.add(jMenuItemDeleteTag); + + jMenuNewItem.setText("Item"); + + jMenuItemNewTask.setText("New Task"); + jMenuItemNewTask.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemNewTaskActionPerformed(evt); + } + }); + jMenuNewItem.add(jMenuItemNewTask); + + jMenuItemNewNote.setText("New Note"); + jMenuItemNewNote.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemNewNoteActionPerformed(evt); + } + }); + jMenuNewItem.add(jMenuItemNewNote); + + jMenuItemNewFile.setText("New File"); + jMenuItemNewFile.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemNewFileActionPerformed(evt); + } + }); + jMenuNewItem.add(jMenuItemNewFile); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("Research Management System"); + setLocationByPlatform(true); + setMinimumSize(new java.awt.Dimension(500, 300)); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosing(java.awt.event.WindowEvent evt) { + formWindowClosing(evt); + } + }); + + jSplitPaneHoriz.setDividerLocation(200); + jSplitPaneHoriz.setResizeWeight(0.25); + jSplitPaneHoriz.setMinimumSize(new java.awt.Dimension(100, 200)); + + jListThreads.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jListThreads.setMinimumSize(new java.awt.Dimension(100, 200)); + jListThreads.setName(""); // NOI18N + jListThreads.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + jListThreadsValueChanged(evt); + } + }); + jScrollPaneThreadList.setViewportView(jListThreads); + + jSplitPaneHoriz.setLeftComponent(jScrollPaneThreadList); + + jSplitPaneVert.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + jSplitPaneVert.setResizeWeight(0.9); + + jScrollPaneTags.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + jPanelTags.setLayout(null); + jScrollPaneTags.setViewportView(jPanelTags); + + jSplitPaneVert.setRightComponent(jScrollPaneTags); + + jPanelThreadContent.setLayout(new java.awt.BorderLayout()); + + jXPanelContent.setScrollableHeightHint(org.jdesktop.swingx.ScrollableSizeHint.NONE); + jXPanelContent.setScrollableWidthHint(org.jdesktop.swingx.ScrollableSizeHint.FIT); + jXPanelContent.setLayout(new javax.swing.BoxLayout(jXPanelContent, javax.swing.BoxLayout.Y_AXIS)); + jScrollPaneContent.setViewportView(jXPanelContent); + + jPanelThreadContent.add(jScrollPaneContent, java.awt.BorderLayout.CENTER); + + jButtonDeleteThread.setText("Delete"); + jButtonDeleteThread.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonDeleteThreadActionPerformed(evt); + } + }); + + jButtoNewFile.setText("New File"); + jButtoNewFile.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtoNewFileActionPerformed(evt); + } + }); + + jButtonNewTask.setText("New Task"); + jButtonNewTask.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonNewTaskActionPerformed(evt); + } + }); + + jButtonNewNote.setText("New Note"); + jButtonNewNote.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonNewNoteActionPerformed(evt); + } + }); + + jLabelThreadName.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabelThreadName.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + jLabelThreadName.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + jLabelThreadNameMouseClicked(evt); + } + }); + + javax.swing.GroupLayout jPanelHeaderLayout = new javax.swing.GroupLayout(jPanelHeader); + jPanelHeader.setLayout(jPanelHeaderLayout); + jPanelHeaderLayout.setHorizontalGroup( + jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelHeaderLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabelThreadName) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 363, Short.MAX_VALUE) + .addComponent(jButtonNewNote) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonNewTask) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtoNewFile) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonDeleteThread)) + ); + jPanelHeaderLayout.setVerticalGroup( + jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButtonDeleteThread) + .addComponent(jButtoNewFile) + .addComponent(jButtonNewTask) + .addComponent(jButtonNewNote) + .addComponent(jLabelThreadName)) + ); + + jPanelThreadContent.add(jPanelHeader, java.awt.BorderLayout.NORTH); + + jSplitPaneVert.setLeftComponent(jPanelThreadContent); + + javax.swing.GroupLayout jPanelThreadInfoLayout = new javax.swing.GroupLayout(jPanelThreadInfo); + jPanelThreadInfo.setLayout(jPanelThreadInfoLayout); + jPanelThreadInfoLayout.setHorizontalGroup( + jPanelThreadInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSplitPaneVert) + ); + jPanelThreadInfoLayout.setVerticalGroup( + jPanelThreadInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSplitPaneVert, javax.swing.GroupLayout.DEFAULT_SIZE, 531, Short.MAX_VALUE) + ); + + jSplitPaneHoriz.setRightComponent(jPanelThreadInfo); + + jMenuNew.setText("New"); + + jMenuItemNewThread.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_T, java.awt.event.InputEvent.CTRL_MASK)); + jMenuItemNewThread.setText("Thread"); + jMenuItemNewThread.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemNewThreadActionPerformed(evt); + } + }); + jMenuNew.add(jMenuItemNewThread); + + jMenuBar.add(jMenuNew); + + jMenuData.setText("Data"); + + jMenuItemSave.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK)); + jMenuItemSave.setText("Save"); + jMenuItemSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemSaveActionPerformed(evt); + } + }); + jMenuData.add(jMenuItemSave); + + jMenuItemReload.setText("Reload"); + jMenuItemReload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemReloadActionPerformed(evt); + } + }); + jMenuData.add(jMenuItemReload); + + jMenuBar.add(jMenuData); + + jMenuFind.setText("Find"); + + jMenuItemShowAll.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, java.awt.event.InputEvent.CTRL_MASK)); + jMenuItemShowAll.setText("Show All"); + jMenuItemShowAll.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemShowAllActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemShowAll); + jMenuFind.add(jSeparator2); + + jMenuItemFindTag.setText("By Tag"); + jMenuItemFindTag.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindTagActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindTag); + + jMenuItemFindDate.setText("By Date Range"); + jMenuItemFindDate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindDateActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindDate); + + jMenuItemFindText.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F, java.awt.event.InputEvent.CTRL_MASK)); + jMenuItemFindText.setText("By Full Text"); + jMenuItemFindText.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindTextActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindText); + jMenuFind.add(jSeparator1); + + jMenuItemFindDeadline.setText("Upcoming Deadlines"); + jMenuItemFindDeadline.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindDeadlineActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindDeadline); + + jMenuItemFindPending.setText("Pending Tasks"); + jMenuItemFindPending.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindPendingActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindPending); + + jMenuItemFindLateTasks.setText("Late Tasks"); + jMenuItemFindLateTasks.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemFindLateTasksActionPerformed(evt); + } + }); + jMenuFind.add(jMenuItemFindLateTasks); + + jMenuBar.add(jMenuFind); + + jMenuHelp.setText("Help"); + + jMenuItemAbout.setText("About"); + jMenuItemAbout.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jMenuItemAboutActionPerformed(evt); + } + }); + jMenuHelp.add(jMenuItemAbout); + + jMenuBar.add(jMenuHelp); + + setJMenuBar(jMenuBar); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getInnerContentPanel()); + getInnerContentPanel().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jSplitPaneHoriz, javax.swing.GroupLayout.DEFAULT_SIZE, 930, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jSplitPaneHoriz, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void initComponentsMore(){ + jPanelTags.setLayout(new WrapLayout(WrapLayout.LEFT, 1, 1)); + + //global keyboard shortcut for "Save" + jSplitPaneHoriz.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(jMenuItemSave.getAccelerator(), "actionSave"); + jSplitPaneHoriz.getActionMap().put("actionSave", ActionSave); + //global keyboard shortcut for "Show All" + jListThreads.getInputMap(JComponent.WHEN_FOCUSED).put(jMenuItemShowAll.getAccelerator(), "actionShowAll");//must override for list + jSplitPaneHoriz.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(jMenuItemShowAll.getAccelerator(), "actionShowAll"); + jSplitPaneHoriz.getActionMap().put("actionShowAll", ActionShowAll); + //global keyboard shortcut for "New Thread" + jSplitPaneHoriz.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(jMenuItemNewThread.getAccelerator(), "actionNewThread"); + jSplitPaneHoriz.getActionMap().put("actionNewThread", ActionNewThread); + //global keyboard shortcut for "Search All" + jSplitPaneHoriz.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(jMenuItemFindText.getAccelerator(), "actionFindAll"); + jSplitPaneHoriz.getActionMap().put("actionFindAll", ActionFindAll); + + + //set the loading panel as the glass pane + setGlassPane(LoadingPanel.instance()); + } + + private void jButtonAddTagActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonAddTagActionPerformed + TagSelectionDialog dialog = new TagSelectionDialog(this); + dialog.showDialog(); + Tag result = dialog.getResult(); + if(result != null){ + getSelectedThread().getTags().add(result); + new WorkerDisplayThreadTags(getSelectedThread()).execute(); + } + }//GEN-LAST:event_jButtonAddTagActionPerformed + + private void jMenuItemDeleteTagActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemDeleteTagActionPerformed + try{ + JMenuItem source = (JMenuItem)evt.getSource(); + JPopupMenu parent = (JPopupMenu)source.getParent(); + TagButton invoker = (TagButton)parent.getInvoker(); + getSelectedThread().getTags().remove(invoker.tag); + new WorkerDisplayThreadTags(getSelectedThread()).execute(); + }catch (ClassCastException ex){} + }//GEN-LAST:event_jMenuItemDeleteTagActionPerformed + + private void jMenuItemNewTaskActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewTaskActionPerformed + Main.newTask(); + }//GEN-LAST:event_jMenuItemNewTaskActionPerformed + + private void jMenuItemNewNoteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewNoteActionPerformed + Main.newNote(); + }//GEN-LAST:event_jMenuItemNewNoteActionPerformed + + private void jMenuItemNewFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewFileActionPerformed + Main.newFile(); + }//GEN-LAST:event_jMenuItemNewFileActionPerformed + + private void jMenuItemNewThreadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewThreadActionPerformed + createAndShowNewThread(); + }//GEN-LAST:event_jMenuItemNewThreadActionPerformed + + private void jMenuItemSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemSaveActionPerformed + new WorkerSaveData().execute(); + }//GEN-LAST:event_jMenuItemSaveActionPerformed + + private void jMenuItemReloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemReloadActionPerformed + String message = "Reloading will cuase you to lose any data added since the last time you saved.\nAre you sure you want to proceed?"; + if(Prompts.getUserApproval(message, PromptType.WARNING)){ + loadStateAndPopulate(); + } + }//GEN-LAST:event_jMenuItemReloadActionPerformed + + private void jMenuItemShowAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemShowAllActionPerformed + ActionShowAll.actionPerformed(evt); + }//GEN-LAST:event_jMenuItemShowAllActionPerformed + + private void jMenuItemFindTagActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindTagActionPerformed + useDialog(new DialogSearchTags(this)); + }//GEN-LAST:event_jMenuItemFindTagActionPerformed + + private void jMenuItemFindDateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindDateActionPerformed + useDialog(new DialogDateRange(this)); + }//GEN-LAST:event_jMenuItemFindDateActionPerformed + + private void jMenuItemFindTextActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindTextActionPerformed + useDialog(new DialogSearchText(this)); + }//GEN-LAST:event_jMenuItemFindTextActionPerformed + + private void jMenuItemFindDeadlineActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindDeadlineActionPerformed + useDialog(new DialogDeadline(this)); + }//GEN-LAST:event_jMenuItemFindDeadlineActionPerformed + + private void jMenuItemFindPendingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindPendingActionPerformed + refreshThreadListAndDisplay(new PendingTaskFinder(), null); + }//GEN-LAST:event_jMenuItemFindPendingActionPerformed + + private void jMenuItemFindLateTasksActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemFindLateTasksActionPerformed + refreshThreadListAndDisplay(new LateTaskFinder(), null); + }//GEN-LAST:event_jMenuItemFindLateTasksActionPerformed + + private void jMenuItemAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemAboutActionPerformed + Package pack = getClass().getPackage(); + String title = pack.getSpecificationTitle(); + String author = pack.getSpecificationVendor(); + String version = pack.getSpecificationVersion(); + String date = pack.getImplementationVersion(); + + if(version != null){ + version = version.replace("_", "."); + } + + String msg = String.format("%s%nby %s%n%nversion: %s%n%s%n", title, author, version, date); + + JOptionPane.showMessageDialog(this, msg, "About", JOptionPane.PLAIN_MESSAGE); + }//GEN-LAST:event_jMenuItemAboutActionPerformed + + private void jListThreadsValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_jListThreadsValueChanged + if(!evt.getValueIsAdjusting()){ + refreshSelectedThread(); + } + }//GEN-LAST:event_jListThreadsValueChanged + + private void jButtonDeleteThreadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonDeleteThreadActionPerformed + promptDelete(); + }//GEN-LAST:event_jButtonDeleteThreadActionPerformed + + private void jButtoNewFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtoNewFileActionPerformed + Main.newFile(); + }//GEN-LAST:event_jButtoNewFileActionPerformed + + private void jButtonNewTaskActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonNewTaskActionPerformed + Main.newTask(); + }//GEN-LAST:event_jButtonNewTaskActionPerformed + + private void jButtonNewNoteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonNewNoteActionPerformed + Main.newNote(); + }//GEN-LAST:event_jButtonNewNoteActionPerformed + + private void jLabelThreadNameMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jLabelThreadNameMouseClicked + promptToNameThread(getSelectedThread(), false); + }//GEN-LAST:event_jLabelThreadNameMouseClicked + + private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing + //Cannot use ActionSave because I need reference to the worker in order to wait + WorkerSaveData worker = new WorkerSaveData(); + worker.execute(); + setVisible(false); + try { + System.out.println("Saving..."); + worker.get(); + } catch (InterruptedException | ExecutionException ex) { + //Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); + //TODO: should probably log this + } + }//GEN-LAST:event_formWindowClosing + + private final Action ActionShowAll = new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + refreshThreadListAndDisplay(); + } + }; + + private final Action ActionSave = new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + new WorkerSaveData().execute(); + } + }; + + private final Action ActionNewThread = new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + createAndShowNewThread(); + } + }; + + private final Action ActionFindAll = new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent evt) { + useDialog(new DialogSearchText(MainFrame.instance())); + } + }; + + private void promptToNameThread(ItemThread sel, boolean isNewThread){ + String prompt = isNewThread ? + "Enter a name for the new thread" : + "Enter new name for the thread (or cancel to keep name)"; + + String resp = Prompts.getUserInput(prompt, PromptType.PLAIN); + if(resp != null){ + sel.setName(resp); + refreshThreadListAndDisplay(null, sel); + } + } + + private void useDialog(BaseDialog dialog){ + dialog.showDialog(); + AbstractFinder result = dialog.getResult(); + if(result != null){ + refreshThreadListAndDisplay(result, null); + } + } + + private void promptDelete(){ + String message = "Deleting this thread cannot be undone.\nAre you sure you want to proceed?"; + if(Prompts.getUserApproval(message, PromptType.WARNING)){ + Main.deleteThread(); + } + } + + private void enableThreadButtons(boolean b){ + //jButtonNewNote.setEnabled(b); + //jButtonNewTask.setEnabled(b); + //jButtoNewFile.setEnabled(b); + jButtonDeleteThread.setEnabled(b); + } + + private JPanel createPanelForItem(Item i){ + if(i instanceof NoteItem){ + return new PanelNoteItem((NoteItem)i); + }else if(i instanceof TaskItem){ + return new PanelTaskItem((TaskItem)i); + }else if(i instanceof FileItem){ + return new PanelFileItem((FileItem)i); + } + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * + */ + private class TagButton extends JButton { + + public final Tag tag; + + public TagButton(final Tag tag) { + this.tag = tag; + this.setBorderPainted(false); + this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5), BorderFactory.createEtchedBorder(EtchedBorder.LOWERED))); + this.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + this.setText(tag.toString()); + + this.setMinimumSize(new Dimension(40,40)); + + this.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + refreshThreadListAndDisplay(new TagFinder(tag), null); + } + }); + + this.addMouseListener(new PopClickListener()); + } + } + + /** + * MouseAdapter for displaying the popup menu on tags + */ + private class PopClickListener extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + doPop(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + doPop(e); + } + } + + private void doPop(MouseEvent e) { + jPopupMenuTag.show(e.getComponent(), e.getX(), e.getY()); + } + } + + private void displayThread(ItemThread toLoad){ + if(toLoad != null){ + //set name and enable buttons + jLabelThreadName.setText(toLoad.getName()); + enableThreadButtons(true); + + new WorkerDisplayThreadItems(toLoad).execute(); + new WorkerDisplayThreadTags(toLoad).execute(); + }else{ + //clear existing content + jXPanelContent.removeAll(); + jPanelTags.removeAll(); + //clear name and disable buttons + jLabelThreadName.setText(""); + enableThreadButtons(false); + } + revalidate(); + repaint(); + } + + private int numLoaders = 0; + + private synchronized void showLoader(){ + numLoaders++; + getGlassPane().setVisible(true); + } + + private synchronized void hideLoader(){ + numLoaders--; + if(numLoaders <= 0){ + getGlassPane().setVisible(false); + } + } + + // + /** + * Task of loading State from file and populating the GUI with information + */ + private class WorkerLoadData extends SwingWorker{ + + private boolean result; + + @Override + protected void done() { + if(result){ + refreshThreadListAndDisplay(); + displayNotification("Data loaded successfully"); + }else{ + Prompts.informUser("Error!", "Unrecoverable error: unable to load data.\nSee log files.", PromptType.ERROR); + System.exit(1);//TODO: is this the correct way to handle this? + } + + hideLoader(); + if(DEBUG_WORKERS)System.out.println("Stopping " + this.getClass().getName()); + } + + @Override + protected Void doInBackground() throws Exception { + if(DEBUG_WORKERS)System.out.println("Starting " + this.getClass().getName()); + showLoader(); + result = Main.loadStateFromFile(); + return null; + } + } + + /** + * Task of saving data from State to file + */ + private class WorkerSaveData extends SwingWorker { + + private boolean result; + + @Override + protected void done() { + if(result){ + displayNotification("Data saved successfully"); + }else{ + Prompts.informUser("Error!", "Unrecoverable error: unable to save data.\nSee log files.", PromptType.ERROR); + System.exit(1);//TODO: is this the correct way to handle this? + } + if(DEBUG_WORKERS)System.out.println("Stopping " + this.getClass().getName()); + } + + @Override + protected Void doInBackground() throws Exception { + if(DEBUG_WORKERS)System.out.println("Starting " + this.getClass().getName()); + //no loader necessary for saving + result = Main.storeStateToFile(); + return null; + } + } + + /** + * Task of refreshing the list of threads in the GUI + */ + private class WorkerRefreshThreadList extends SwingWorker{ + + private final AbstractFinder finder; + private final ItemThread toDisplay; + + public WorkerRefreshThreadList(AbstractFinder finder, ItemThread toDisplay){ + this.finder = finder; + this.toDisplay = toDisplay; + } + + @Override + protected void done() { + clearDisplayedThread(); + setSelectedThread(toDisplay); + hideLoader(); + if(DEBUG_WORKERS)System.out.println("Stopping " + this.getClass().getName()); + } + + @Override + protected Void doInBackground() throws Exception { + if(DEBUG_WORKERS)System.out.println("Starting " + this.getClass().getName()); + showLoader(); + jListThreads.setModel(new SearchSortItemThreadListModel(true, finder)); + return null; + } + } + + /** + * Loads the items in the given thread to the GUI + */ + private class WorkerDisplayThreadItems extends SwingWorker{ + + private final ItemThread toLoad; + + public WorkerDisplayThreadItems(ItemThread toLoad){ + this.toLoad = toLoad; + } + + @Override + protected void done() { + //relayout the UI + jPanelThreadContent.updateUI(); + + //update the scroll bar position + JScrollBar sb = jScrollPaneContent.getVerticalScrollBar(); + sb.setValue( sb.getMaximum() ); + + hideLoader(); + if(DEBUG_WORKERS)System.out.println("Stopping " + this.getClass().getName()); + } + + @Override + protected Void doInBackground() throws Exception { + if(DEBUG_WORKERS)System.out.println("Starting " + this.getClass().getName()); + showLoader(); + + //clear existing content + jXPanelContent.removeAll(); + + //load items + if(toLoad.size() == 0){ + jXPanelContent.add(jPanelEmptyThread); + }else{ + for(Item i : toLoad){ + jXPanelContent.add(createPanelForItem(i)); + } + } + + return null; + } + } + + /** + * Loads the items in the given thread to the GUI + */ + private class WorkerDisplayThreadTags extends SwingWorker{ + + private final ItemThread toLoad; + + public WorkerDisplayThreadTags(ItemThread toLoad){ + this.toLoad = toLoad; + } + + @Override + protected void done() { + //relayout the UI + jScrollPaneTags.updateUI(); + + hideLoader(); + if(DEBUG_WORKERS)System.out.println("Stopping " + this.getClass().getName()); + } + + @Override + protected Void doInBackground() throws Exception { + if(DEBUG_WORKERS)System.out.println("Starting " + this.getClass().getName()); + showLoader(); + + //clear existing content + jPanelTags.removeAll(); + + //load tags + for(Tag t : toLoad.getTags()){ + jPanelTags.add(new TagButton(t)); + } + jPanelTags.add(jButtonAddTag); + + return null; + } + } + // + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtoNewFile; + private javax.swing.JButton jButtonAddTag; + private javax.swing.JButton jButtonDeleteThread; + private javax.swing.JButton jButtonNewNote; + private javax.swing.JButton jButtonNewTask; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabelThreadName; + private javax.swing.JList jListThreads; + private javax.swing.JMenuBar jMenuBar; + private javax.swing.JMenu jMenuData; + private javax.swing.JMenu jMenuFind; + private javax.swing.JMenu jMenuHelp; + private javax.swing.JMenuItem jMenuItemAbout; + private javax.swing.JMenuItem jMenuItemDeleteTag; + private javax.swing.JMenuItem jMenuItemFindDate; + private javax.swing.JMenuItem jMenuItemFindDeadline; + private javax.swing.JMenuItem jMenuItemFindLateTasks; + private javax.swing.JMenuItem jMenuItemFindPending; + private javax.swing.JMenuItem jMenuItemFindTag; + private javax.swing.JMenuItem jMenuItemFindText; + private javax.swing.JMenuItem jMenuItemNewFile; + private javax.swing.JMenuItem jMenuItemNewNote; + private javax.swing.JMenuItem jMenuItemNewTask; + private javax.swing.JMenuItem jMenuItemNewThread; + private javax.swing.JMenuItem jMenuItemReload; + private javax.swing.JMenuItem jMenuItemSave; + private javax.swing.JMenuItem jMenuItemShowAll; + private javax.swing.JMenu jMenuNew; + private javax.swing.JMenu jMenuNewItem; + private javax.swing.JPanel jPanelEmptyThread; + private javax.swing.JPanel jPanelHeader; + private javax.swing.JPanel jPanelTags; + private javax.swing.JPanel jPanelThreadContent; + private javax.swing.JPanel jPanelThreadInfo; + private javax.swing.JPopupMenu jPopupMenuTag; + private javax.swing.JScrollPane jScrollPaneContent; + private javax.swing.JScrollPane jScrollPaneTags; + private javax.swing.JScrollPane jScrollPaneThreadList; + private javax.swing.JPopupMenu.Separator jSeparator1; + private javax.swing.JPopupMenu.Separator jSeparator2; + private javax.swing.JSplitPane jSplitPaneHoriz; + private javax.swing.JSplitPane jSplitPaneVert; + private org.jdesktop.swingx.JXPanel jXPanelContent; + // End of variables declaration//GEN-END:variables + + public static MainFrame instance(){ + if(inst == null){ + inst = new MainFrame(); + } + return inst; + } + + /** + * Creates a new {@link ItemThread} and displays it in the UI + * @return + */ + public ItemThread createAndShowNewThread(){ + ItemThread thread = Main.getState().createNewThread(); + promptToNameThread(thread, true); + return thread; + } + + /** + * + * @return the {@link ItemThread} currently selected in the thread list + */ + public ItemThread getSelectedThread(){ + return (ItemThread)jListThreads.getSelectedValue(); + } + + /** + * Select the given {@link ItemThread} in the thread list + * @param thread + */ + public void setSelectedThread(ItemThread thread){ + jListThreads.setSelectedValue(thread, true); + } + + /** + * Clear the selected thread in the thread list + */ + public void clearSelectedThread(){ + jListThreads.clearSelection(); + } + + /** + * Clear the thread currently + */ + public void clearDisplayedThread(){ + displayThread(null); + } + + /** + * Reload the display for the currently selected thread + */ + public void refreshSelectedThread(){ + displayThread(getSelectedThread()); + } + + /** + * Refresh the thread list, showing all item threads, without selecting one. + */ + public void refreshThreadListAndDisplay(){ + refreshThreadListAndDisplay(null, null); + } + + /** + * Refresh the thread list, showing only threads that match the given filter + * and selecting the item given + * @param filter the thread filter to apply + * @param toDisplay the item to select after refreshing (may be null) + */ + public void refreshThreadListAndDisplay(AbstractFinder filter, ItemThread toDisplay){ + new WorkerRefreshThreadList(filter, toDisplay).execute(); + } + + /** + * Load the state from file and display it in the GUI + */ + public void loadStateAndPopulate(){ + new WorkerLoadData().execute(); + } +} diff --git a/ResearchManagementSystem/src/rms/view/SearchSortItemThreadListModel.java b/ResearchManagementSystem/src/rms/view/SearchSortItemThreadListModel.java new file mode 100644 index 0000000..2e92ad8 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/SearchSortItemThreadListModel.java @@ -0,0 +1,46 @@ +package rms.view; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.swing.AbstractListModel; +import rms.model.item.ItemThread; +import rms.control.search.AbstractFinder; +import rms.control.Main; + +/** + * List model that supports filtering and sorting + * + * @author Timothy + */ +public class SearchSortItemThreadListModel extends AbstractListModel { + private final List items; + + public SearchSortItemThreadListModel(boolean sorted, AbstractFinder finder) { + Set itemThreads = Main.getState().getThreads(); + + //optionally, apply a filter + if(finder != null){ + itemThreads = finder.find(itemThreads); + } + + //create the list of items + items = new ArrayList<>(itemThreads); + + //optionally, sort the list + if(sorted){ + Collections.sort(items); + } + } + + @Override + public int getSize() { + return items.size(); + } + + @Override + public Object getElementAt(int i) { + return items.get(i); + } +} diff --git a/ResearchManagementSystem/src/rms/view/TagSelectionDialog.form b/ResearchManagementSystem/src/rms/view/TagSelectionDialog.form new file mode 100644 index 0000000..118831a --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/TagSelectionDialog.form @@ -0,0 +1,95 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/TagSelectionDialog.java b/ResearchManagementSystem/src/rms/view/TagSelectionDialog.java new file mode 100644 index 0000000..793ed7b --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/TagSelectionDialog.java @@ -0,0 +1,161 @@ +package rms.view; + +import java.awt.Frame; +import rms.control.Main; +import rms.model.Tag; +import rms.view.util.JPanelTagSelection; +import rms.view.util.JPanelTagSelection.ItemChosenListener; +import rms.view.util.Prompts; +import rms.view.util.Prompts.PromptType; + +/** + * Dialog for selecting a {@link Tag} from those available or creating a new one + * + * @author Timothy + */ +public class TagSelectionDialog extends javax.swing.JDialog { + + private Tag selection = null; + + /** + * Creates new form TagsDialog + * @param parent + */ + public TagSelectionDialog(Frame parent) { + super(parent, true); + initComponents(); + initComponentsMore(); + setLocationRelativeTo(parent); + getRootPane().setDefaultButton(jButtonOk); + } + + public void showDialog(){ + setVisible(true); + } + + /** + * @return the {@link Tag} selected in the UI + */ + public Tag getResult(){ + return selection; + } + + private void close(Tag t){ + selection = t; + setVisible(false); + } + + private void initComponentsMore(){ + jPanelTagSelection.addItemChosenListener(new ItemChosenListener(){ + @Override + public void itemChosen(JPanelTagSelection.ItemChosenEvent m) { + close(m.getSelection()); + } + }); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jButtonCancel = new javax.swing.JButton(); + jButtonOk = new javax.swing.JButton(); + jButtonNewTag = new javax.swing.JButton(); + jPanelTagSelection = new rms.view.util.JPanelTagSelection(Main.getState().getTags()); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setMinimumSize(new java.awt.Dimension(250, 250)); + + jButtonCancel.setText("Cancel"); + jButtonCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonCancelActionPerformed(evt); + } + }); + + jButtonOk.setText("Ok"); + jButtonOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonOkActionPerformed(evt); + } + }); + + jButtonNewTag.setText("New Tag"); + jButtonNewTag.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonNewTagActionPerformed(evt); + } + }); + + jPanelTagSelection.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + jPanelTagSelectionMouseClicked(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanelTagSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jButtonNewTag) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jButtonOk) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonCancel))) + .addGap(10, 10, 10)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(jPanelTagSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButtonNewTag) + .addComponent(jButtonCancel) + .addComponent(jButtonOk)) + .addGap(10, 10, 10)) + ); + + pack(); + }// //GEN-END:initComponents + + private void jButtonOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOkActionPerformed + close(jPanelTagSelection.getResult()); + }//GEN-LAST:event_jButtonOkActionPerformed + + private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed + close(null); + }//GEN-LAST:event_jButtonCancelActionPerformed + + private void jButtonNewTagActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonNewTagActionPerformed + String newName = Prompts.getUserInput("Enter new tag name", PromptType.PLAIN); + if(newName != null && !newName.isEmpty()){ + //create new tag and add it globally + Tag newTag = Main.getState().newTag(newName); + //close the list marking the new tag as selected + close(newTag); + } + }//GEN-LAST:event_jButtonNewTagActionPerformed + + private void jPanelTagSelectionMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jPanelTagSelectionMouseClicked + System.out.println("CLICK"); + }//GEN-LAST:event_jPanelTagSelectionMouseClicked + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtonCancel; + private javax.swing.JButton jButtonNewTag; + private javax.swing.JButton jButtonOk; + private rms.view.util.JPanelTagSelection jPanelTagSelection; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.form b/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.form new file mode 100644 index 0000000..9407eda --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.form @@ -0,0 +1,245 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.java b/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.java new file mode 100644 index 0000000..75f1806 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelAnyTextItem.java @@ -0,0 +1,361 @@ +package rms.view.item; + +import java.awt.Color; +import java.awt.Container; +import java.util.Date; +import java.util.logging.Logger; +import javax.swing.UIDefaults; +import rms.model.item.TextItem; +import rms.view.util.Prompts; +import rms.view.util.Prompts.PromptType; + +/** + * Generic panel for displaying a {@link TextItem} + * + * @author Timothy + */ +public abstract class PanelAnyTextItem extends javax.swing.JPanel { + + private static final Logger thisLog = Logger.getLogger(PanelAnyTextItem.class.getName()); + + public enum StatusIndicator {PENDING, COMPLETE, OVERDUE}; + + private static final String SAVE = "Save"; + private static final String EDIT = "Edit"; + + protected final TextItem item; + + /** + * Creates new form PanelNoteItem + * @param itm + */ + public PanelAnyTextItem(TextItem itm) { + initComponents(); + changeDescPaneColor(Color.LIGHT_GRAY); + + item = itm; + + reflectItemChanges(); + } + + private void reflectItemChanges(){ + jLabelType.setText(item.getItemTypeName()); + jLabelID.setText(String.format("%05d", item.getID())); + jLabelDateCreated.setText(item.getCreationTime().toString()); + jLabelDateModified.setText(item.getModificationTime().toString()); + jTextPaneDesc.setText(item.getText()); + + switch(getStatus()){ + case PENDING: + jLabelStatus.setText("Pending"); + jLabelStatus.setForeground(Color.yellow); + break; + case COMPLETE: + jLabelStatus.setText("Completed"); + jLabelStatus.setForeground(new Color(0,175,0)); + break; + case OVERDUE: + jLabelStatus.setText("Overdue"); + jLabelStatus.setForeground(Color.red); + break; + default: + jLabelStatus.setText("unavailable"); + jLabelStatus.setForeground(Color.black); + } + } + + protected final void showStatusPanel(boolean b){ + jPanelStatus.setVisible(b); + } + + //NOTE: The 3 abstract methods here exist because the UI was created to + // support one of two subtypes of TextItem. This is a misuse of inheritance. + + protected abstract StatusIndicator getStatus(); + + protected abstract void togglePressed(); + + protected abstract void deadlineUpdated(Date newDate); + + protected void setDeadline(Date newDate){ + jXDatePickerDeadline.setDate(newDate); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanelHeader = new javax.swing.JPanel(); + jLabelType = new javax.swing.JLabel(); + jLabelID = new javax.swing.JLabel(); + jLabelCreated = new javax.swing.JLabel(); + jLabelDateCreated = new javax.swing.JLabel(); + jLabelModified = new javax.swing.JLabel(); + jLabelDateModified = new javax.swing.JLabel(); + jButtonEdit = new javax.swing.JButton(); + jButtonDelete = new javax.swing.JButton(); + jLabelType1 = new javax.swing.JLabel(); + jPanelStatus = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + jXDatePickerDeadline = new org.jdesktop.swingx.JXDatePicker(); + jLabelStatus = new javax.swing.JLabel(); + jButtonToggleStatus = new javax.swing.JButton(); + jPanelText = new javax.swing.JPanel(); + jTextPaneDesc = new javax.swing.JTextPane(); + + setName(""); // NOI18N + + jLabelType.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabelType.setText("Type"); + + jLabelID.setText("00000"); + + jLabelCreated.setText("Created:"); + + jLabelDateCreated.setText("Date"); + + jLabelModified.setText("Modified:"); + + jLabelDateModified.setText("Date"); + + jButtonEdit.setText("Edit"); + jButtonEdit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonEditActionPerformed(evt); + } + }); + + jButtonDelete.setText("Delete"); + jButtonDelete.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonDeleteActionPerformed(evt); + } + }); + + jLabelType1.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabelType1.setText(":"); + + javax.swing.GroupLayout jPanelHeaderLayout = new javax.swing.GroupLayout(jPanelHeader); + jPanelHeader.setLayout(jPanelHeaderLayout); + jPanelHeaderLayout.setHorizontalGroup( + jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelHeaderLayout.createSequentialGroup() + .addGroup(jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelHeaderLayout.createSequentialGroup() + .addComponent(jLabelCreated) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelDateCreated) + .addGap(27, 27, 27) + .addComponent(jLabelModified) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelDateModified)) + .addGroup(jPanelHeaderLayout.createSequentialGroup() + .addComponent(jLabelType) + .addGap(0, 0, 0) + .addComponent(jLabelType1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelID))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jButtonEdit) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonDelete)) + ); + jPanelHeaderLayout.setVerticalGroup( + jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelHeaderLayout.createSequentialGroup() + .addGroup(jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelType) + .addComponent(jLabelID) + .addComponent(jLabelType1)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelCreated) + .addComponent(jLabelDateCreated) + .addComponent(jLabelModified) + .addComponent(jLabelDateModified))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButtonEdit) + .addComponent(jButtonDelete)) + ); + + jLabel1.setText("Deadline:"); + + jXDatePickerDeadline.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jXDatePickerDeadlineActionPerformed(evt); + } + }); + + jLabelStatus.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabelStatus.setText("status"); + + jButtonToggleStatus.setText("Toggle Status"); + jButtonToggleStatus.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonToggleStatusActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanelStatusLayout = new javax.swing.GroupLayout(jPanelStatus); + jPanelStatus.setLayout(jPanelStatusLayout); + jPanelStatusLayout.setHorizontalGroup( + jPanelStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelStatusLayout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jXDatePickerDeadline, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 107, Short.MAX_VALUE) + .addComponent(jLabelStatus) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jButtonToggleStatus)) + ); + jPanelStatusLayout.setVerticalGroup( + jPanelStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jXDatePickerDeadline, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1) + .addComponent(jLabelStatus) + .addComponent(jButtonToggleStatus)) + ); + + jTextPaneDesc.setEditable(false); + jTextPaneDesc.setName(""); // NOI18N + jTextPaneDesc.addFocusListener(new java.awt.event.FocusAdapter() { + public void focusLost(java.awt.event.FocusEvent evt) { + jTextPaneDescFocusLost(evt); + } + }); + + javax.swing.GroupLayout jPanelTextLayout = new javax.swing.GroupLayout(jPanelText); + jPanelText.setLayout(jPanelTextLayout); + jPanelTextLayout.setHorizontalGroup( + jPanelTextLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTextPaneDesc) + ); + jPanelTextLayout.setVerticalGroup( + jPanelTextLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTextPaneDesc) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jPanelStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanelHeader, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanelText, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(10, 10, 10)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(jPanelHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanelStatus, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanelText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(10, 10, 10)) + ); + }// //GEN-END:initComponents + + private void jButtonEditActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonEditActionPerformed + switch (jButtonEdit.getText()) { + case SAVE: + saveAction(); + break; + case EDIT: + editAction(); + break; + } + }//GEN-LAST:event_jButtonEditActionPerformed + + private void jTextPaneDescFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_jTextPaneDescFocusLost + if(evt.getOppositeComponent() != jButtonEdit){ + saveAction(); + } + }//GEN-LAST:event_jTextPaneDescFocusLost + + private void jButtonToggleStatusActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonToggleStatusActionPerformed + togglePressed(); + reflectItemChanges(); + }//GEN-LAST:event_jButtonToggleStatusActionPerformed + + private void jButtonDeleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonDeleteActionPerformed + if(Prompts.getUserApproval("Are you sure you want to delete this item?", PromptType.QUESTION)){ + deleteItem(); + } + }//GEN-LAST:event_jButtonDeleteActionPerformed + + private void jXDatePickerDeadlineActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jXDatePickerDeadlineActionPerformed + deadlineUpdated(jXDatePickerDeadline.getDate()); + reflectItemChanges(); + }//GEN-LAST:event_jXDatePickerDeadlineActionPerformed + + private void deleteItem(){ + item.getThread().remove(item); + //cheap way to update UI + Container p = this.getParent(); + p.remove(this); + p.revalidate(); + p.repaint(); + } + + private void editAction(){ + jTextPaneDesc.setEditable(true); + jTextPaneDesc.requestFocusInWindow(); + changeDescPaneColor(Color.WHITE); + jButtonEdit.setText(SAVE); + reflectItemChanges(); + } + + private void saveAction(){ + jTextPaneDesc.setEditable(false); + jButtonEdit.setText(EDIT); + changeDescPaneColor(Color.LIGHT_GRAY); + + String oldText = item.getText(); + String newText = jTextPaneDesc.getText(); + if(!newText.equals(oldText)){ + item.replaceText(newText); + } + + reflectItemChanges(); + } + + private void changeDescPaneColor(Color c){ + UIDefaults defaults = new UIDefaults(); + defaults.put("TextPane[Enabled].backgroundPainter", c); + jTextPaneDesc.putClientProperty("Nimbus.Overrides", defaults); + jTextPaneDesc.putClientProperty("Nimbus.Overrides.InheritDefaults", true); + jTextPaneDesc.setBackground(c); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtonDelete; + private javax.swing.JButton jButtonEdit; + private javax.swing.JButton jButtonToggleStatus; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabelCreated; + private javax.swing.JLabel jLabelDateCreated; + private javax.swing.JLabel jLabelDateModified; + private javax.swing.JLabel jLabelID; + private javax.swing.JLabel jLabelModified; + private javax.swing.JLabel jLabelStatus; + private javax.swing.JLabel jLabelType; + private javax.swing.JLabel jLabelType1; + private javax.swing.JPanel jPanelHeader; + private javax.swing.JPanel jPanelStatus; + private javax.swing.JPanel jPanelText; + private javax.swing.JTextPane jTextPaneDesc; + private org.jdesktop.swingx.JXDatePicker jXDatePickerDeadline; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/item/PanelFileItem.form b/ResearchManagementSystem/src/rms/view/item/PanelFileItem.form new file mode 100644 index 0000000..0ee7cea --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelFileItem.form @@ -0,0 +1,146 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/src/rms/view/item/PanelFileItem.java b/ResearchManagementSystem/src/rms/view/item/PanelFileItem.java new file mode 100644 index 0000000..82539c5 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelFileItem.java @@ -0,0 +1,181 @@ +package rms.view.item; + +import java.awt.Container; +import java.awt.Desktop; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import rms.model.item.FileItem; +import rms.view.util.Prompts; + +/** + * Panel for displaying a {@link FileItem} + * + * @author Timothy + */ +public class PanelFileItem extends javax.swing.JPanel { + + private static final Logger thisLog = Logger.getLogger(PanelFileItem.class.getName()); + + private final FileItem item; + + /** + * Creates new form PanelNoteItem + * @param itm + */ + public PanelFileItem(FileItem itm) { + initComponents(); + + item = itm; + + reflectItemChanges(); + } + + + private void reflectItemChanges(){ + jLabelID.setText(String.format("%05d", item.getID())); + jLabelDateCreated.setText(item.getCreationTime().toString()); + jLabelDateModified.setText(item.getModificationTime().toString()); + jButtonFileLink.setText(item.getFile().getName()); + } + + + private void deleteItem(){ + item.getThread().remove(item); + //cheap way to update UI + Container p = this.getParent(); + p.remove(this); + p.revalidate(); + p.repaint(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabelType = new javax.swing.JLabel(); + jLabelDateCreated = new javax.swing.JLabel(); + jButtonDelete = new javax.swing.JButton(); + jLabelCreated = new javax.swing.JLabel(); + jLabelModified = new javax.swing.JLabel(); + jLabelDateModified = new javax.swing.JLabel(); + jLabelID = new javax.swing.JLabel(); + jButtonFileLink = new javax.swing.JButton(); + + setPreferredSize(new java.awt.Dimension(345, 95)); + + jLabelType.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabelType.setText("File Import:"); + + jLabelDateCreated.setText("Date"); + + jButtonDelete.setText("Delete"); + jButtonDelete.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonDeleteActionPerformed(evt); + } + }); + + jLabelCreated.setText("Imported:"); + + jLabelModified.setText("Modified:"); + + jLabelDateModified.setText("Date"); + + jLabelID.setText("00000"); + + jButtonFileLink.setForeground(new java.awt.Color(0, 0, 255)); + jButtonFileLink.setText("Open File"); + jButtonFileLink.setBorderPainted(false); + jButtonFileLink.setContentAreaFilled(false); + jButtonFileLink.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + jButtonFileLink.setFocusPainted(false); + jButtonFileLink.setMargin(new java.awt.Insets(0, 0, 0, 0)); + jButtonFileLink.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonFileLinkActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jButtonFileLink) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabelCreated) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelDateCreated) + .addGap(18, 18, 18) + .addComponent(jLabelModified) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelDateModified)) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabelType) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabelID))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 62, Short.MAX_VALUE) + .addComponent(jButtonDelete))) + .addGap(10, 10, 10)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelType) + .addComponent(jLabelID)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelCreated) + .addComponent(jLabelDateCreated) + .addComponent(jLabelModified) + .addComponent(jLabelDateModified))) + .addComponent(jButtonDelete, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonFileLink) + .addGap(10, 10, 10)) + ); + }// //GEN-END:initComponents + + private void jButtonDeleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonDeleteActionPerformed + if(Prompts.getUserApproval("Are you sure you want to delete this item?", Prompts.PromptType.QUESTION)){ + item.getFile().delete(); + deleteItem(); + } + }//GEN-LAST:event_jButtonDeleteActionPerformed + + private void jButtonFileLinkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonFileLinkActionPerformed + try { + Desktop.getDesktop().open(item.getFile()); + } catch (IOException ex) { + thisLog.log(Level.SEVERE, "Unable to open file.", ex); + } + }//GEN-LAST:event_jButtonFileLinkActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtonDelete; + private javax.swing.JButton jButtonFileLink; + private javax.swing.JLabel jLabelCreated; + private javax.swing.JLabel jLabelDateCreated; + private javax.swing.JLabel jLabelDateModified; + private javax.swing.JLabel jLabelID; + private javax.swing.JLabel jLabelModified; + private javax.swing.JLabel jLabelType; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/item/PanelNoteItem.java b/ResearchManagementSystem/src/rms/view/item/PanelNoteItem.java new file mode 100644 index 0000000..879a8b6 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelNoteItem.java @@ -0,0 +1,32 @@ +package rms.view.item; + +import java.util.Date; +import rms.model.item.NoteItem; + +/** + * Panel for displaying a {@link NoteItem} + * + * @author Timothy + */ +public class PanelNoteItem extends PanelAnyTextItem { + + public PanelNoteItem(NoteItem itm) { + super(itm); + showStatusPanel(false); + } + + @Override + protected StatusIndicator getStatus(){ + return StatusIndicator.COMPLETE; + } + + @Override + protected void togglePressed(){ + //NOTHING + } + + @Override + protected void deadlineUpdated(Date newDate) { + //NOTHING + } +} diff --git a/ResearchManagementSystem/src/rms/view/item/PanelTaskItem.java b/ResearchManagementSystem/src/rms/view/item/PanelTaskItem.java new file mode 100644 index 0000000..a44db7e --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/item/PanelTaskItem.java @@ -0,0 +1,48 @@ +package rms.view.item; + +import java.util.Date; +import rms.model.item.TaskItem; + +/** + * Panel for displaying a {@link TaskItem} + * + * @author Timothy + */ +public class PanelTaskItem extends PanelAnyTextItem { + + public PanelTaskItem(TaskItem itm) { + super(itm); + showStatusPanel(true); + setDeadline(itm.getDeadline()); + } + + @Override + protected StatusIndicator getStatus(){ + TaskItem ti = (TaskItem)item; + + if(ti.isComplete()){ + return StatusIndicator.COMPLETE; + }else if(ti.isOverdue()){ + return StatusIndicator.OVERDUE; + }else { + return StatusIndicator.PENDING; + } + } + + @Override + protected void togglePressed(){ + TaskItem ti = (TaskItem)item; + + if(ti.isComplete()){ + ti.markActive(); + }else{ + ti.markCompleted(); + } + } + + @Override + protected void deadlineUpdated(Date newDate) { + TaskItem ti = (TaskItem)item; + ti.setDeadline(newDate); + } +} diff --git a/ResearchManagementSystem/src/rms/view/search/BaseDialog.form b/ResearchManagementSystem/src/rms/view/search/BaseDialog.form new file mode 100644 index 0000000..0bc8cad --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/BaseDialog.form @@ -0,0 +1,79 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/search/BaseDialog.java b/ResearchManagementSystem/src/rms/view/search/BaseDialog.java new file mode 100644 index 0000000..ff99d6e --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/BaseDialog.java @@ -0,0 +1,138 @@ +package rms.view.search; + +import java.awt.Container; +import java.awt.Frame; +import rms.control.search.AbstractFinder; + +/** + * Generic dialog box providing Ok and Cancel functionality + * + * @author Timothy + */ +public class BaseDialog extends javax.swing.JDialog { + + private boolean approved; + + /** + * No-arg constructor for Bean creation + */ + public BaseDialog() { + this(null, true); + } + + /** + * Creates new form DialogDateRange + * @param parent + * @param modal + */ + public BaseDialog(Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + setLocationRelativeTo(parent); + getRootPane().setDefaultButton(jButtonOk); + approved = false; + } + + public void showDialog(){ + setVisible(true); + } + + /** + * NOTE: you must override this method or getResult() will + * always return null + * @return + */ + protected AbstractFinder createFinder(){ + return null; + } + + public AbstractFinder getResult(){ + AbstractFinder retVal = null; + if(approved){ + retVal = createFinder(); + } + return retVal; + } + + public Container getInnerContentPanel() { + return jPanelContent; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jButtonCancel = new javax.swing.JButton(); + jButtonOk = new javax.swing.JButton(); + jPanelContent = new javax.swing.JPanel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Search"); + + jButtonCancel.setText("Cancel"); + jButtonCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonCancelActionPerformed(evt); + } + }); + + jButtonOk.setText("Ok"); + jButtonOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonOkActionPerformed(evt); + } + }); + + jPanelContent.setLayout(new java.awt.BorderLayout()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 178, Short.MAX_VALUE) + .addComponent(jButtonOk) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonCancel)) + .addComponent(jPanelContent, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(10, 10, 10)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(jPanelContent, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButtonCancel) + .addComponent(jButtonOk)) + .addGap(10, 10, 10)) + ); + + pack(); + }// //GEN-END:initComponents + + private void jButtonOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOkActionPerformed + approved = true; + setVisible(false); + }//GEN-LAST:event_jButtonOkActionPerformed + + private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed + approved = false; + setVisible(false); + }//GEN-LAST:event_jButtonCancelActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtonCancel; + private javax.swing.JButton jButtonOk; + private javax.swing.JPanel jPanelContent; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/search/BaseDialogBeanInfo.java b/ResearchManagementSystem/src/rms/view/search/BaseDialogBeanInfo.java new file mode 100644 index 0000000..7aab9f3 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/BaseDialogBeanInfo.java @@ -0,0 +1,36 @@ +package rms.view.search; + +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.beans.SimpleBeanInfo; +import javax.swing.JDialog; + +/** + * + * @author Timothy + */ +public class BaseDialogBeanInfo extends SimpleBeanInfo { + @Override + public BeanDescriptor getBeanDescriptor() { + BeanDescriptor desc = new BeanDescriptor(BaseDialog.class); + desc.setValue("containerDelegate", "getInnerContentPanel"); + return desc; + } + + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return super.getPropertyDescriptors(); + } + + @Override + public BeanInfo[] getAdditionalBeanInfo() { + try { + return new BeanInfo[] { Introspector.getBeanInfo(JDialog.class) }; + } catch (IntrospectionException ex) { + return super.getAdditionalBeanInfo(); + } + } +} \ No newline at end of file diff --git a/ResearchManagementSystem/src/rms/view/search/DialogDateRange.form b/ResearchManagementSystem/src/rms/view/search/DialogDateRange.form new file mode 100644 index 0000000..715b7a8 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogDateRange.form @@ -0,0 +1,105 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/search/DialogDateRange.java b/ResearchManagementSystem/src/rms/view/search/DialogDateRange.java new file mode 100644 index 0000000..963376c --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogDateRange.java @@ -0,0 +1,104 @@ +package rms.view.search; + +import java.util.Date; +import rms.control.search.AbstractFinder; +import rms.control.search.DateRangeFinder; +import rms.util.DateHelpers; +import rms.util.DateRangeType; + +/** + * Dialog for selecting a start and end date (for searching) + * + * @author Timothy + */ +public class DialogDateRange extends BaseDialog { + + /** + * Creates new form DialogDateRange + * @param parent + */ + public DialogDateRange(java.awt.Frame parent) { + super(parent, true); + initComponents(); + } + + @Override + protected AbstractFinder createFinder() { + DateRangeType selectionType = (DateRangeType)jComboBoxType.getSelectedItem(); + Date startDate = jXDatePickerStart.getDate(); + Date endDate = jXDatePickerEnd.getDate(); + return new DateRangeFinder(selectionType, startDate, endDate); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jComboBoxType = new javax.swing.JComboBox<>(DateRangeType.values()); + jLabel2 = new javax.swing.JLabel(); + jXDatePickerStart = new org.jdesktop.swingx.JXDatePicker(); + jLabel3 = new javax.swing.JLabel(); + jXDatePickerEnd = new org.jdesktop.swingx.JXDatePicker(); + + setMinimumSize(new java.awt.Dimension(373, 161)); + + jLabel1.setText("Find items"); + + jComboBoxType.setToolTipText(""); + + jLabel2.setText("between"); + + jXDatePickerStart.setDate(DateHelpers.Today()); + + jLabel3.setText("and"); + + jXDatePickerEnd.setDate(DateHelpers.Today()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getInnerContentPanel()); + getInnerContentPanel().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jComboBoxType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2)) + .addGroup(layout.createSequentialGroup() + .addComponent(jXDatePickerStart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jXDatePickerEnd, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jComboBoxType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel2)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jXDatePickerStart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3) + .addComponent(jXDatePickerEnd, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + ); + + pack(); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox jComboBoxType; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private org.jdesktop.swingx.JXDatePicker jXDatePickerEnd; + private org.jdesktop.swingx.JXDatePicker jXDatePickerStart; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/search/DialogDeadline.form b/ResearchManagementSystem/src/rms/view/search/DialogDeadline.form new file mode 100644 index 0000000..9a9b5fe --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogDeadline.form @@ -0,0 +1,107 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/search/DialogDeadline.java b/ResearchManagementSystem/src/rms/view/search/DialogDeadline.java new file mode 100644 index 0000000..27d8821 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogDeadline.java @@ -0,0 +1,78 @@ +package rms.view.search; + +import rms.control.search.AbstractFinder; +import rms.control.search.UpcomingTaskFinder; +import rms.util.DateUnit; + +/** + * Dialog for selecting a date unit and increment (for searching) + * + * @author Timothy + */ +public class DialogDeadline extends BaseDialog { + + /** + * Creates new form DialogDeadline + * @param parent + */ + public DialogDeadline(java.awt.Frame parent) { + super(parent, true); + initComponents(); + } + + @Override + protected AbstractFinder createFinder() { + int increment = Integer.valueOf((String)jComboBoxIncrement.getSelectedItem()); + DateUnit unit = (DateUnit)jComboBoxUnit.getSelectedItem(); + return new UpcomingTaskFinder(increment, unit); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jComboBoxIncrement = new javax.swing.JComboBox(); + jComboBoxUnit = new javax.swing.JComboBox<>(DateUnit.values()); + + setMinimumSize(new java.awt.Dimension(374, 133)); + + jLabel1.setText("Find pending items due in the next"); + + jComboBoxIncrement.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30" })); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getInnerContentPanel()); + getInnerContentPanel().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jComboBoxIncrement, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jComboBoxUnit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jComboBoxIncrement, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jComboBoxUnit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox jComboBoxIncrement; + private javax.swing.JComboBox jComboBoxUnit; + private javax.swing.JLabel jLabel1; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.form b/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.form new file mode 100644 index 0000000..b8693f5 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.form @@ -0,0 +1,44 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.java b/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.java new file mode 100644 index 0000000..98797f4 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogSearchTags.java @@ -0,0 +1,81 @@ +package rms.view.search; + +import rms.control.Main; +import rms.control.search.AbstractFinder; +import rms.control.search.TagFinder; +import rms.view.util.JPanelTagSelection; + +/** + * Dialog for selecting a {@link Tag} (for searching) + * + * @author Timothy + */ +public class DialogSearchTags extends BaseDialog { + + private boolean tagChosenSpecial; + + /** + * Creates new form DialogSearchTags + * @param parent + */ + public DialogSearchTags(java.awt.Frame parent) { + super(parent, true); + tagChosenSpecial = false; + initComponents(); + initComponentsMore(); + } + + @Override + protected AbstractFinder createFinder() { + return new TagFinder(jPanelTagSelection.getResult()); + } + + private void initComponentsMore(){ + jPanelTagSelection.addItemChosenListener(new JPanelTagSelection.ItemChosenListener(){ + @Override + public void itemChosen(JPanelTagSelection.ItemChosenEvent m) { + tagChosenSpecial = true; + setVisible(false); + } + }); + } + + @Override + public AbstractFinder getResult(){ + if(tagChosenSpecial){ + return createFinder(); + }else{ + return super.getResult(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanelTagSelection = new rms.view.util.JPanelTagSelection(Main.getState().getTags()); + + setMinimumSize(new java.awt.Dimension(332, 295)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getInnerContentPanel()); + getInnerContentPanel().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanelTagSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanelTagSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + pack(); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private rms.view.util.JPanelTagSelection jPanelTagSelection; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/search/DialogSearchText.form b/ResearchManagementSystem/src/rms/view/search/DialogSearchText.form new file mode 100644 index 0000000..ad21db4 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogSearchText.form @@ -0,0 +1,55 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/search/DialogSearchText.java b/ResearchManagementSystem/src/rms/view/search/DialogSearchText.java new file mode 100644 index 0000000..c0c6601 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/search/DialogSearchText.java @@ -0,0 +1,71 @@ +package rms.view.search; + +import rms.control.search.AbstractFinder; +import rms.control.search.TextFinder; + +/** + * Dialog for entering a string of text (for searching) + * + * @author Timothy + */ +public class DialogSearchText extends BaseDialog { + + /** + * Creates new form DialogSearchText + * @param parent + */ + public DialogSearchText(java.awt.Frame parent) { + super(parent, true); + initComponents(); + } + + @Override + protected AbstractFinder createFinder() { + String searchText = jTextFieldSearchText.getText(); + if(searchText != null && !searchText.isEmpty()){ + return new TextFinder(searchText); + } + return null; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jTextFieldSearchText = new javax.swing.JTextField(); + jLabel1 = new javax.swing.JLabel(); + + setMinimumSize(new java.awt.Dimension(332, 157)); + + jLabel1.setText("Find items containing the text"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getInnerContentPanel()); + getInnerContentPanel().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addGap(0, 122, Short.MAX_VALUE)) + .addComponent(jTextFieldSearchText) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jTextFieldSearchText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + private javax.swing.JTextField jTextFieldSearchText; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.form b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.form new file mode 100644 index 0000000..2045c2b --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.form @@ -0,0 +1,57 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.java b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.java new file mode 100644 index 0000000..f77c92a --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection.java @@ -0,0 +1,156 @@ +package rms.view.util; + +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import rms.model.Tag; + +/** + * Generic panel for listing tags and instantly searching the list + * + * @author Timothy + */ +public class JPanelTagSelection extends javax.swing.JPanel { + + private final ArrayList listeners; + private final Set tagList; + + /** + * Creates new form TagSelectionPanel + */ + public JPanelTagSelection() { + this(new HashSet()); + } + + public JPanelTagSelection(Set existingTags){ + this.tagList = existingTags; + this.listeners = new ArrayList<>(); + initComponents(); + jTextFieldSearch.getDocument().addDocumentListener(searchTextListener); + displayList(); + } + + /** + * Show only items containing the given string (case insensitive) + * @param searchVal + */ + private void displayListFiltered(String searchVal){ + jListTags.setModel(new SearchTagsListModel(tagList, searchVal)); + } + + private void displayList(){ + displayListFiltered(""); + } + + private final DocumentListener searchTextListener = new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + displayListFiltered(jTextFieldSearch.getText()); + } + + @Override + public void removeUpdate(DocumentEvent e) { + displayListFiltered(jTextFieldSearch.getText()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + displayListFiltered(jTextFieldSearch.getText()); + } + }; + + public Tag getResult(){ + return (Tag)jListTags.getSelectedValue(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jScrollPane1 = new javax.swing.JScrollPane(); + jListTags = new javax.swing.JList(); + jTextFieldSearch = new javax.swing.JTextField(); + + jListTags.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jListTags.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + jListTagsMouseClicked(evt); + } + }); + jScrollPane1.setViewportView(jListTags); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTextFieldSearch, javax.swing.GroupLayout.DEFAULT_SIZE, 273, Short.MAX_VALUE) + .addComponent(jScrollPane1) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jTextFieldSearch, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void jListTagsMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jListTagsMouseClicked + if (evt.getClickCount() == 2 && evt.getButton() == MouseEvent.BUTTON1) { + /* + System.out.println("Double click on " + evt); + System.out.println(jListTags.locationToIndex(evt.getPoint())); + + System.out.println(getResult()); + */ + ItemChosenEvent newEvent = new ItemChosenEvent(evt.getSource(), getResult()); + synchronized (this) { + for (ItemChosenListener l : listeners) { + l.itemChosen(newEvent); // fire the event to this listener + } + } + + } + }//GEN-LAST:event_jListTagsMouseClicked + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList jListTags; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JTextField jTextFieldSearch; + // End of variables declaration//GEN-END:variables + + public synchronized void addItemChosenListener(ItemChosenListener hearer) { + listeners.add(hearer); + } + + public synchronized void removeItemChosenListener(ItemChosenListener hearer) { + listeners.remove(hearer); + } + + public static class ItemChosenEvent extends java.util.EventObject { + + private final Tag selection; + + public ItemChosenEvent(Object source, Tag selection) { + super(source); + this.selection = selection; + } + + public Tag getSelection(){ + return selection; + } + } + + public interface ItemChosenListener extends java.util.EventListener { + void itemChosen(ItemChosenEvent m); + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.form b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.form new file mode 100644 index 0000000..46f626b --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.form @@ -0,0 +1,70 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.java b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.java new file mode 100644 index 0000000..1838956 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/JPanelTagSelection2.java @@ -0,0 +1,187 @@ +package rms.view.util; + +import java.awt.BorderLayout; +import java.awt.Component; +import javax.swing.JFrame; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; + +/** + * Generic panel for listing tags and instantly searching the list. Supports + * selecting multiple tags. + * + * TODO: incomplete + * + * @author Timothy + */ +public class JPanelTagSelection2 extends javax.swing.JPanel { + + /** + * Creates new form JPanelTagSelection2 + */ + public JPanelTagSelection2() { + initComponents(); + initComponentsMore(); + } + + private void initComponentsMore(){ + if (jTable1.getColumnModel().getColumnCount() > 0) { + int width = 50; + jTable1.getColumnModel().getColumn(0).setPreferredWidth(width); + jTable1.getColumnModel().getColumn(0).setMaxWidth(width); + jTable1.getColumnModel().getColumn(0).setMinWidth(width); + } + + + TableCellRenderer cellRenderer0 = jTable1.getCellRenderer(0, 0); + TableCellRenderer cellRenderer1 = jTable1.getCellRenderer(0, 1); + + /* READ THESE: + http://stackoverflow.com/questions/9294108/is-there-a-way-to-add-a-row-selected-listener-on-jtable + http://stackoverflow.com/questions/20327005/jtable-actionlistener-for-select-a-row + + would this listener fire before the table is repainted so I could just not select the checkbox row? + */ + + //TODO: Maybe need to override BooleanRenderer from Jtable for the Boolean row + MyCellRenderer cellRenderer = new MyCellRenderer(); + jTable1.setDefaultRenderer(String.class, new DefaultTableCellRenderer(){ + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBorder(noFocusBorder); + return this; + } + + }); + + //jTable1.setDefaultRenderer(Boolean.class, new Boolean); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jScrollPane1 = new javax.swing.JScrollPane(); + jTable1 = new javax.swing.JTable(); + jTextFieldSearch = new javax.swing.JTextField(); + + jTable1.setAutoCreateRowSorter(true); + jTable1.setModel(new MyTableModel()); + jTable1.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN); + jTable1.setRowSelectionAllowed(true); + jTable1.setShowHorizontalLines(false); + jTable1.setShowVerticalLines(false); + jTable1.getTableHeader().setReorderingAllowed(false); + jScrollPane1.setViewportView(jTable1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jTextFieldSearch, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)) + .addGap(0, 0, 0)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(jTextFieldSearch, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 241, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JTable jTable1; + private javax.swing.JTextField jTextFieldSearch; + // End of variables declaration//GEN-END:variables + + private class MyTableModel extends AbstractTableModel { + + private final Object[][] data; + + public MyTableModel() { + //TODO: this is temp data + data = new Object[][]{ + {true, "one"}, + {true, "two"}, + {false, "three"}, + {false, "four"}, + {true, "five"} + }; + } + + @Override + public int getRowCount() { + return data.length; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return data[rowIndex][columnIndex]; + } + + @Override + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + + @Override + public boolean isCellEditable(int row, int col) { + return col == 0; + } + + /* + * Don't need to implement this method unless your table's + * data can change. + */ + @Override + public void setValueAt(Object value, int row, int col) { + data[row][col] = value; + fireTableCellUpdated(row, col); + } + + } + + public class MyCellRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBorder(noFocusBorder); + return this; + } + } + + public static void main(String[] args) { + final JFrame x = new JFrame(); + x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + x.add(new JPanelTagSelection2(), BorderLayout.CENTER); + x.pack(); + + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + x.setVisible(true); + } + }); + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/LoadingPanel.form b/ResearchManagementSystem/src/rms/view/util/LoadingPanel.form new file mode 100644 index 0000000..93d3777 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/LoadingPanel.form @@ -0,0 +1,36 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/src/rms/view/util/LoadingPanel.java b/ResearchManagementSystem/src/rms/view/util/LoadingPanel.java new file mode 100644 index 0000000..d0c078c --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/LoadingPanel.java @@ -0,0 +1,98 @@ +package rms.view.util; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import javax.swing.JPanel; + +/** + * Panel showing a loading gif and blocking all input to the underlying components + * @author Timothy + */ +public class LoadingPanel extends JPanel implements FocusListener, MouseListener { + + private static LoadingPanel inst = null; + + private LoadingPanel() { + initComponents(); + } + + public static LoadingPanel instance(){ + if(inst == null){ + inst = new LoadingPanel(); + inst.addFocusListener(inst); + inst.addMouseListener(inst); + } + return inst; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + + setOpaque(false); + setLayout(new java.awt.BorderLayout()); + + jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/rms/view/util/loading.gif"))); // NOI18N + add(jLabel1, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + // End of variables declaration//GEN-END:variables + + @Override + public void setVisible(boolean aFlag) { + if (aFlag) { + //grab the focus to capture all key events + requestFocus(); + } + super.setVisible(aFlag); + } + + @Override + public void focusGained(FocusEvent e) { + //Nothing + } + + @Override + public void focusLost(FocusEvent e) { + //make sure focus is not lost while visible + if (isVisible()) { + requestFocus(); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + //Just ignore all mouse events + } + + @Override + public void mousePressed(MouseEvent e) { + //Just ignore all mouse events + } + + @Override + public void mouseReleased(MouseEvent e) { + //Just ignore all mouse events + } + + @Override + public void mouseEntered(MouseEvent e) { + //Just ignore all mouse events + } + + @Override + public void mouseExited(MouseEvent e) { + //Just ignore all mouse events + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/NotificationFrame.form b/ResearchManagementSystem/src/rms/view/util/NotificationFrame.form new file mode 100644 index 0000000..31a423c --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/NotificationFrame.form @@ -0,0 +1,118 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchManagementSystem/src/rms/view/util/NotificationFrame.java b/ResearchManagementSystem/src/rms/view/util/NotificationFrame.java new file mode 100644 index 0000000..56f694b --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/NotificationFrame.java @@ -0,0 +1,157 @@ +package rms.view.util; + +import java.awt.Color; +import java.awt.Container; +import javax.swing.BorderFactory; +import javax.swing.JButton; + +/** + * Generic frame for displaying notification messages over the main content + * + * @author Timothy + */ +public class NotificationFrame extends javax.swing.JFrame { + + /** + * Creates new form NotificationFrame + */ + public NotificationFrame() { + initComponents(); + setLocationRelativeTo(null); + } + + /** + * + * @return the Container to which content should be added + */ + public Container getInnerContentPanel() { + return jPanelMainContent; + } + + /** + * Displays a notification with the given string for a short period of time + * @param message + */ + public void displayNotification(String message){ + new Notification(message).display(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLayeredPane1 = new javax.swing.JLayeredPane(); + jPanelMainContent = new javax.swing.JPanel(); + jPanelEast = new javax.swing.JPanel(); + jPanelSouth = new javax.swing.JPanel(); + jPanelNotifications = new javax.swing.JPanel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); + + jLayeredPane1.setLayout(new javax.swing.OverlayLayout(jLayeredPane1)); + + javax.swing.GroupLayout jPanelMainContentLayout = new javax.swing.GroupLayout(jPanelMainContent); + jPanelMainContent.setLayout(jPanelMainContentLayout); + jPanelMainContentLayout.setHorizontalGroup( + jPanelMainContentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + jPanelMainContentLayout.setVerticalGroup( + jPanelMainContentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + + jLayeredPane1.add(jPanelMainContent); + + jPanelEast.setOpaque(false); + jPanelEast.setLayout(new java.awt.BorderLayout()); + + jPanelSouth.setOpaque(false); + jPanelSouth.setLayout(new java.awt.BorderLayout()); + + jPanelNotifications.setOpaque(false); + jPanelNotifications.setLayout(new javax.swing.BoxLayout(jPanelNotifications, javax.swing.BoxLayout.Y_AXIS)); + jPanelSouth.add(jPanelNotifications, java.awt.BorderLayout.SOUTH); + + jPanelEast.add(jPanelSouth, java.awt.BorderLayout.EAST); + + jLayeredPane1.add(jPanelEast); + jLayeredPane1.setLayer(jPanelEast, javax.swing.JLayeredPane.DRAG_LAYER); + + getContentPane().add(jLayeredPane1, java.awt.BorderLayout.CENTER); + + pack(); + }// //GEN-END:initComponents + + private class Notification extends JButton { + + public Notification(String message) { + this.setText(formatText(message)); + this.setBackground(Color.gray); + this.setBorderPainted(false); + this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7), BorderFactory.createLineBorder(Color.darkGray, 2, true))); + this.setCursor(null); + this.setAlignmentX(RIGHT_ALIGNMENT); + this.setHorizontalAlignment(LEFT); + this.setRolloverEnabled(false); + this.setFocusable(false); + } + + private String formatText(String input){ + final int LL = 30; + + StringBuffer sb_in = new StringBuffer(input); + StringBuilder sb_out = new StringBuilder(""); + while(sb_in.length() > LL){ + sb_out.append(sb_in.substring(0, LL)).append("
"); + sb_in.delete(0, LL); + } + sb_out.append(String.format("%s", sb_in)); + sb_out.append(""); + + return sb_out.toString(); + } + + public void display(){ + new NoteLifetime(this).start(); + } + } + + + private class NoteLifetime extends Thread{ + + private final Notification note; + + public NoteLifetime(Notification note) { + this.note = note; + } + + @Override + public void run() { + try { + jPanelNotifications.add(note); + revalidate(); + repaint(); + Thread.sleep(2000); + } catch (InterruptedException ex) { + }finally{ + jPanelNotifications.remove(note); + revalidate(); + repaint(); + } + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLayeredPane jLayeredPane1; + private javax.swing.JPanel jPanelEast; + private javax.swing.JPanel jPanelMainContent; + private javax.swing.JPanel jPanelNotifications; + private javax.swing.JPanel jPanelSouth; + // End of variables declaration//GEN-END:variables +} diff --git a/ResearchManagementSystem/src/rms/view/util/NotificationFrameBeanInfo.java b/ResearchManagementSystem/src/rms/view/util/NotificationFrameBeanInfo.java new file mode 100644 index 0000000..ca812a1 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/NotificationFrameBeanInfo.java @@ -0,0 +1,36 @@ +package rms.view.util; + +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.beans.SimpleBeanInfo; +import javax.swing.JFrame; + +/** + * + * @author Timothy + */ +public class NotificationFrameBeanInfo extends SimpleBeanInfo { + @Override + public BeanDescriptor getBeanDescriptor() { + BeanDescriptor desc = new BeanDescriptor(NotificationFrame.class); + desc.setValue("containerDelegate", "getInnerContentPanel"); + return desc; + } + + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return super.getPropertyDescriptors(); + } + + @Override + public BeanInfo[] getAdditionalBeanInfo() { + try { + return new BeanInfo[] { Introspector.getBeanInfo(JFrame.class) }; + } catch (IntrospectionException ex) { + return super.getAdditionalBeanInfo(); + } + } +} \ No newline at end of file diff --git a/ResearchManagementSystem/src/rms/view/util/Prompts.java b/ResearchManagementSystem/src/rms/view/util/Prompts.java new file mode 100644 index 0000000..e47edb7 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/Prompts.java @@ -0,0 +1,71 @@ +package rms.view.util; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; +import rms.view.MainFrame; + +/** + * Utility class for obtaining information from the user via dialogs + * + * @author Timothy + */ +public class Prompts { + + private static final Logger thisLog = Logger.getLogger(Prompts.class.getName()); + + public static enum PromptType {PLAIN, INFO, ERROR, WARNING, QUESTION}; + + /** + * + * @param prompt + * @param type + * @return + */ + public static boolean getUserApproval(String prompt, PromptType type){ + String title = "Response needed"; + int intType = convert(type); + int option = JOptionPane.showConfirmDialog(MainFrame.instance(), prompt, title, JOptionPane.YES_NO_OPTION, intType); + return option == JOptionPane.YES_OPTION; + } + + /** + * + * @param prompt + * @param type + * @return user entered value or null if the user Cancelled + */ + public static String getUserInput(String prompt, PromptType type){ + String title = "Response needed"; + int intType = convert(type); + return JOptionPane.showInputDialog(MainFrame.instance(), prompt, title, intType); + } + + /** + * + * @param title + * @param prompt + * @param type + */ + public static void informUser(String title, String prompt, PromptType type){ + JOptionPane.showMessageDialog(MainFrame.instance(), prompt, title, convert(type)); + } + + private static int convert(PromptType type){ + switch(type){ + case PLAIN: + return JOptionPane.PLAIN_MESSAGE; + case INFO: + return JOptionPane.INFORMATION_MESSAGE; + case ERROR: + return JOptionPane.ERROR_MESSAGE; + case WARNING: + return JOptionPane.WARNING_MESSAGE; + case QUESTION: + return JOptionPane.QUESTION_MESSAGE; + default: + thisLog.log(Level.WARNING, "Undefined enum value."); + return JOptionPane.PLAIN_MESSAGE; + } + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/SearchTagsListModel.java b/ResearchManagementSystem/src/rms/view/util/SearchTagsListModel.java new file mode 100644 index 0000000..e88232d --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/SearchTagsListModel.java @@ -0,0 +1,37 @@ +package rms.view.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.swing.AbstractListModel; +import rms.model.Tag; +import rms.util.Helpers; +/** + * + * @author Timothy + */ +public class SearchTagsListModel extends AbstractListModel { + private final List items; + + public SearchTagsListModel(Set allTags, String searchString) { + items = new ArrayList<>(); + + for(Tag t : allTags){ + if(Helpers.containsIgnoreCase(t.toString(), searchString)){ + items.add(t); + } + } + Collections.sort(items); + } + + @Override + public int getSize() { + return items.size(); + } + + @Override + public Object getElementAt(int i) { + return items.get(i); + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/WrapLayout.java b/ResearchManagementSystem/src/rms/view/util/WrapLayout.java new file mode 100644 index 0000000..e815c47 --- /dev/null +++ b/ResearchManagementSystem/src/rms/view/util/WrapLayout.java @@ -0,0 +1,175 @@ +package rms.view.util; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Insets; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +/** + * FlowLayout subclass that fully supports wrapping of components. + * Source: http://www.camick.com/java/source/WrapLayout.java + */ +public class WrapLayout extends FlowLayout { + + private Dimension preferredLayoutSize; + + /** + * Constructs a new WrapLayout with a left alignment and a + * default 5-unit horizontal and vertical gap. + */ + public WrapLayout() { + super(); + } + + /** + * Constructs a new FlowLayout with the specified alignment and + * a default 5-unit horizontal and vertical gap. The value of the alignment + * argument must be one of WrapLayout, WrapLayout, + * or WrapLayout. + * + * @param align the alignment value + */ + public WrapLayout(int align) { + super(align); + } + + /** + * Creates a new flow layout manager with the indicated alignment and the + * indicated horizontal and vertical gaps. + *

+ * The value of the alignment argument must be one of + * WrapLayout, WrapLayout, or + * WrapLayout. + * + * @param align the alignment value + * @param hgap the horizontal gap between components + * @param vgap the vertical gap between components + */ + public WrapLayout(int align, int hgap, int vgap) { + super(align, hgap, vgap); + } + + /** + * Returns the preferred dimensions for this layout given the + * visible components in the specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container + */ + @Override + public Dimension preferredLayoutSize(Container target) { + return layoutSize(target, true); + } + + /** + * Returns the minimum dimensions needed to layout the visible + * components contained in the specified target container. + * + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the subcomponents of the + * specified container + */ + @Override + public Dimension minimumLayoutSize(Container target) { + Dimension minimum = layoutSize(target, false); + minimum.width -= (getHgap() + 1); + return minimum; + } + + /** + * Returns the minimum or preferred dimension needed to layout the target + * container. + * + * @param target target to get layout size for + * @param preferred should preferred size be calculated + * @return the dimension to layout the target container + */ + private Dimension layoutSize(Container target, boolean preferred) { + synchronized (target.getTreeLock()) { + // Each row must fit with the width allocated to the containter. + // When the container width = 0, the preferred width of the container + // has not yet been calculated so lets ask for the maximum. + + int targetWidth = target.getSize().width; + + if (targetWidth == 0) { + targetWidth = Integer.MAX_VALUE; + } + + int hgap = getHgap(); + int vgap = getVgap(); + Insets insets = target.getInsets(); + int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); + int maxWidth = targetWidth - horizontalInsetsAndGap; + + // Fit components into the allowed width + Dimension dim = new Dimension(0, 0); + int rowWidth = 0; + int rowHeight = 0; + + int nmembers = target.getComponentCount(); + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + + if (m.isVisible()) { + Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); + + // Can't add the component to current row. Start a new row. + if (rowWidth + d.width > maxWidth) { + addRow(dim, rowWidth, rowHeight); + rowWidth = 0; + rowHeight = 0; + } + + // Add a horizontal gap for all components after the first + if (rowWidth != 0) { + rowWidth += hgap; + } + + rowWidth += d.width; + rowHeight = Math.max(rowHeight, d.height); + } + } + + addRow(dim, rowWidth, rowHeight); + + dim.width += horizontalInsetsAndGap; + dim.height += insets.top + insets.bottom + vgap * 2; + + // When using a scroll pane or the DecoratedLookAndFeel we need to + // make sure the preferred size is less than the size of the + // target containter so shrinking the container size works + // correctly. Removing the horizontal gap is an easy way to do this. + Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); + + if (scrollPane != null && target.isValid()) { + dim.width -= (hgap + 1); + } + + return dim; + } + } + + /* + * A new row has been completed. Use the dimensions of this row + * to update the preferred size for the container. + * + * @param dim update the width and height when appropriate + * @param rowWidth the width of the row to add + * @param rowHeight the height of the row to add + */ + private void addRow(Dimension dim, int rowWidth, int rowHeight) { + dim.width = Math.max(dim.width, rowWidth); + + if (dim.height > 0) { + dim.height += getVgap(); + } + + dim.height += rowHeight; + } +} diff --git a/ResearchManagementSystem/src/rms/view/util/loading.gif b/ResearchManagementSystem/src/rms/view/util/loading.gif new file mode 100644 index 0000000..0ca7ada Binary files /dev/null and b/ResearchManagementSystem/src/rms/view/util/loading.gif differ