Matlab GUI Tutorial - Basic Data Processing Tool: Quan Quach
Matlab GUI Tutorial - Basic Data Processing Tool: Quan Quach
Introduction
This tutorial outlines the basic skeleton for a data processing GUI. The tutorial provides you with the visual aspect of the GUI via download, and then goes on to provide the code to each component callback. This tutorial draws upon many of the basic GUI elements: adding files to a listbox, parsing data, plotting data onto the GUI, saving GUI plots, disabling/enabling buttons, exporting data to Excel format, and many other things. If you just want to see the finished product, you can download the source files here. The source files include the GUI files, the m-files for the sub functions, and 3 sample data files. You can play around with this GUI to learn more about how GUIs are designed and coded. You can also use this GUI as a foundation for developing more advanced GUIs. I highly encourage that you take the time to look through the source code, and to hack it to your content. Feel free to modify it so that it suits your purposes.
This tutorial is written for those with a good amount of experience creating a Matlab GUI. If youre new to creating GUIs in Matlab, you should visit this tutorial first. Basic/Advanced knowledge of Matlab is highly recommended. Matlab version 2007a is used in writing this tutorial. Both earlier versions and new versions should be compatible as well (as long as it isant too outdated). Lets get started!
3. Choose to open the GUI by clicking on Open Existing GUI. Click on Browse to locate where you saved the GUI files.
4. Here is what the GUI should look like when you open it:
5. Click on the
6. The m-file is bare and contains no code for each of the callbacks. The code will be added and explained in the subsequent sections.
41. But what happens if you accidentally added too many files? This is what the delete button is for! Add the following code underneath the deleteFiles_pushbutton_Callback:
42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. %get the current list of file names from the listbox inputFileNames = get(handles.inputFiles_listbox,'String'); %get the values for the selected file names option = get(handles.inputFiles_listbox,'Value'); %is there is nothing to delete, nothing happens if (isempty(option) == 1 || option(1) == 0 ) return end %erases the contents of highlighted item in data array inputFileNames(option) = []; %updates the gui, erasing the selected item from the listbox set(handles.inputFiles_listbox,'String',inputFileNames);
%moves the highlighted item to an appropiate value or else will get error 60. if option(end) > length(inputFileNames) 61. set(handles.inputFiles_listbox,'Value',length(inputFileNames)); 62. end 63. 64. % Update handles structure guidata(hObject, handles);
To allow multiple files to be selected within the listbox, you must add the following code to the opening function. Alternatively, you can change these properties through the Property Inspector using GUIDE.
set(handles.inputFiles_listbox,'Max',2); set(handles.inputFiles_listbox,'Min',0);
65. After you have added in the code, you should test the GUI to make sure that it works. There are some sample test files included in the zip file that you just downloaded. You can try adding/removing these files to the GUI. The menu below pops up when you click on the Add Files button.
1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 2 9 3 0 3 1 3 2 3 3 3 4 3 5 3 6 3 7 3 8 3 9
refresh(data_processing_tool); %initialize the cell arrays %if you don't know what cell arrays are, it might be a good idea to %review this by using the Matlab Help Files handles.data = {}; handles.legendData = {}; for x = 1 : length(inputFileNames) %gets the filename without the extension [ignore,fileName,ext,ignore]=fileparts(inputFileNames{x}); %store filenames so that it will display on the legend handles.legendData(x) = {fileName}; %stores the numerical data using the custom function handles.data{x} = importMyData(inputFileNames{x}); end handles.legendObject = plotData(handles.data,handles.legendData,handles.axes1,get(handles.plot_p opupmenu,'Value')); %the data must be done processing before other Callbacks will be %able to function properly. this variable will be used as a "check" %to see whether the data has been processed or not handles.processDataCompleted = 1; %data is done processing, so re-enable the buttons enableButtons(handles); guidata(hObject, handles);
1. The first thing you might notice is the disableButtons and enableButtons functions. Basically, these functions are included so that while Matlab is busy processing the
data, the user cannot click on any of the other buttons. To learn more about this feature, click here to visit the post that covers this topic. The code for the two functions are:
2. function disableButtons(handles) 3. set(handles.figure1,'Pointer','watch'); 4. set(handles.start_pushbutton,'Enable','off'); 5. set(handles.reset_pushbutton,'Enable','off'); 6. set(handles.addFiles_pushbutton,'Enable','off'); 7. set(handles.savePlot_pushbutton,'Enable','off'); 8. set(handles.deleteFiles_pushbutton,'Enable','off'); 9. set(handles.inputFiles_listbox,'Enable','off'); 10. set(handles.plot_popupmenu,'Enable','off'); set(handles.export_pushbutton,'Enable','off'); function enableButtons(handles) set(handles.figure1,'Pointer','arrow'); set(handles.start_pushbutton,'Enable','on'); set(handles.reset_pushbutton,'Enable','on'); set(handles.addFiles_pushbutton,'Enable','on'); set(handles.savePlot_pushbutton,'Enable','on'); set(handles.deleteFiles_pushbutton,'Enable','on'); set(handles.inputFiles_listbox,'Enable','on'); set(handles.plot_popupmenu,'Enable','on'); set(handles.export_pushbutton,'Enable','on');
11. The next function you might have questions about is the importMyData function. This function is a custom function that parses the input data. For more information on parsing data files, click here to visit the data parsing post. In this tutorial, the sample data files being parsed have three columns of data: frequency, magnitude, and phase. The parsing function takes an input file and outputs the frequency, magnitude, and phase data into a matrix. The following is the code for the parsing function.
12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. function [parsedData]= importMyData(name) %this function parses data files that have some header content %and some ending content %the input to this function is the file name of the data file. %If the data file is not in the current Matlab directory %you must include the entire directory path. %opens the file fid = fopen(name); %reads line one by one into a cell a= 1; line = 0; while line ~= -1 line = fgetl(fid); data{a} = line; a = a +1; end data(end) = []; %get rid of the last line fclose(fid); %this for loop determines where the numerical data starts for p=1:length(data) if (~isempty(str2num(data{p}))) break; end end %this loop saves the numerical data into parsedData %until the numerical data stops
41. for x=p:length(data) 42. if(isempty(str2num(data{x}))) break; end 43. temp = str2num(data{x}); 44. parsedData(x-p+1,:) = temp; end
Note that if you plan to parse your own data which uses a different data file format, you will probably have to play around with the parsing function to get it to parse your data correctly. Also, notice how the data returned from importMyData is stored into a cell, and not a matrix. You can tell that it is a cell by the curly brackets used, {}. Cell arrays can be very versatile, so if you dont know how to use them, you should consult the Matlab help!! 45. Finally, the last custom function within this callback is the plotData function. Writing a separate custom function for plotting the data can be extremely useful because you can call it later on in any of the callbacks if you need to. This function returns the legend object of the plot so that it can be used later in other parts of this GUI. The function takes in 4 arguments. The data that will be plotted, the legend data, the axes that the data will be plotted on (this argument is helpful if you have more than 1 axes on your GUI), and the type of plot.
46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. function [legendObject]=plotData(data,legendData,axesName,option) cla(axesName); %clear the axes axes(axesName); %set the axes to plot hold on grid on %plot the magnitude plot if (option==1) for x=1:(length(data)) plot(data{x}(:,1),data{x}(:,2)); end %add a legend to the plot legendObject = legend(legendData,'Location','Best'); title('Insert Title Here') xlabel('Frequency (GHz)') ylabel('Magnitude (dB)'); %else plot the phase plot else for x=1:(length(data)) plot(data{x}(:,1),data{x}(:,3)); end %add a legend to the plot legendObject= legend(legendData,'Location','Best'); title('Insert Title Here') xlabel('Frequency (GHz)') ylabel('Phase (Degrees)'); end hold off %allow legend titles to be displayed properly set(legendObject,'Interpreter','none');
Once again, this plot function is catered to the sample data files that I created. You will probably have to make some minor mods to it if you plan to use it to process other types of data files.
After adding in all of this code, it would be a good idea to test out the GUI. Run the GUI, add the input files, and process the data. You should get something that looks like this (click on figure to enlarge):
Click here if you need a refresher on how Pop-up Menus work. 15. The next callback is the savePlot_pushbutton_Callback. For more information on this, you can read this post here. In this callback, we put the handles.legendObject to use. The handles.legendObject, if you remember, is an output from the plotData function.
16. 17. 18. 19. 20. 21. 22. 23. 24. %if the data hasn't been processed yet, %nothing happens when this button is pressed if (handles.processDataCompleted == 0) return end disableButtons(handles); refresh(data_processing_tool); savePlotWithinGUI(handles.axes1,handles.legendObject);
25. enableButtons(handles); guidata(hObject, handles); function savePlotWithinGUI(axesObject, legendObject) %this function takes in two arguments %axesObject is the axes object that will be saved (required input) %legendObject is the legend object that will be saved (optional input) %stores savepath for the phase plot [filename, pathname] = uiputfile({ '*.emf','Enhanced Meta File (*.emf)';... '*.bmp','Bitmap (*.bmp)'; '*.fig','Figure (*.fig)'}, ... 'Save picture as','default'); %if user cancels save command, nothing happens if isequal(filename,0) || isequal(pathname,0) return end %create a new figure newFig = figure; %get the units and position of the axes object axes_units = get(axesObject,'Units'); axes_pos = get(axesObject,'Position'); %copies axesObject onto new figure axesObject2 = copyobj(axesObject,newFig); %realign the axes object on the new figure set(axesObject2,'Units',axes_units); set(axesObject2,'Position',[15 5 axes_pos(3) axes_pos(4)]); %if a legendObject was passed to this function . . . if (exist('legendObject')) %get the units and position of the legend object legend_units = get(legendObject,'Units'); legend_pos = get(legendObject,'Position'); %copies the legend onto the the new figure legendObject2 = copyobj(legendObject,newFig); %realign the legend object on the new figure set(legendObject2,'Units',legend_units); set(legendObject2,'Position',[15-axes_pos(1)+legend_pos(1) 5axes_pos(2)+legend_pos(2) legend_pos(3) legend_pos(4)] ); end %adjusts the new figure accordingly set(newFig,'Units',axes_units); set(newFig,'Position',[15 5 axes_pos(3)+30 axes_pos(4)+10]); %saves the plot saveas(newFig,fullfile(pathname, filename)) %closes the figure close(newFig)
Now, run the GUI to check that the pop-up menu and save plot functionality work. Specifically, lets try to select the Phase option on the pop-up menu. This is what you should get (click on figure to enlarge):
Add this code as its own separate m-file, or add it to the data-processing-tool.m file.
function saveDataToExcel(data, fileNames) %stores savepath for the phase plot [filename, pathname] = uiputfile({'*.xls','Excel (*.xls)'},'Save Data to Excel File','default'); %it is assumed that the frequency range is the same for all the data sets magnitudeData = [data{1}(:,1)]; phaseData = [data{1}(:,1)]; for x = 1:length(fileNames) magnitudeData = [magnitudeData data{x}(:,2)]; phaseData = [phaseData data{x}(:,2)]; magDB{x} = 'Mag (dB)'; phaseDegrees{x} = 'Phase(Degrees)'; end saveFileName = fullfile(pathname, filename); xlswrite(saveFileName,['Data File' fileNames],'Magnitude Data','A1');
xlswrite(saveFileName,['Frequency (GHz)' magDB],'Magnitude Data','A2'); xlswrite(saveFileName,phaseData,'Magnitude Data','A3'); xlswrite(saveFileName,['Data file: ' fileNames],'Phase Data','A1'); xlswrite(saveFileName,['Frequency (GHz)' phaseDegrees],'Phase Data','A2'); xlswrite(saveFileName,phaseData,'Phase Data','A3'); deleteEmptyExcelSheets(saveFileName);
And to erase those empty sheets when the Excel file is created, we can use the following function that is discussed in this post.
function deleteEmptyExcelSheets(fileName) %this function erases any empty sheets in an excel document %the input fileName is the entire path of the file %for example, fileName = 'C:\Documents and Settings\matlab\myExcelFile.xls' excelObj = actxserver('Excel.Application'); %opens up an excel object excelWorkbook = excelObj.workbooks.Open(fileName); worksheets = excelObj.sheets; %total number of sheets in workbook numSheets = worksheets.Count; count=1; for x=1:numSheets %stores the current number of sheets in the workbook %this number will change if sheets are deleted temp = worksheets.count; %if there's only one sheet left, we must leave it or else %there will be an error. if (temp == 1) break; end %this command will only delete the sheet if it is empty worksheets.Item(count).Delete; %if a sheet was not deleted, we move on to the next one %by incrementing the count variable if (temp == worksheets.count) count = count + 1; end end excelWorkbook.Save; excelWorkbook.Close(false); excelObj.Quit; delete(excelObj);
Once again, you should try testing the GUI to make sure it works, and to test the exporting capabilties. There are many different ways you can export your data to excel. For instance, you can export a separate Excel file for each input file, or you can lump all the magnitude data together into one file, and lump all the phase data into another file. The function that is used in this example does the latter.
For more information on the Close GUI Confirmation, you can visit this post.
function closeGUI selection = questdlg('Do you want to close the GUI?',... 'Close Request Function',... 'Yes','No','Yes'); switch selection, case 'Yes', delete(gcf) case 'No' return end
16. Now, for the reset button. Resetting your GUI to the default state can save the user a lot of time and fustration. Instead of closing and opening the GUI to get to the starting state, the user can simply click on the reset button.
17. function reset_pushbutton_Callback(hObject, eventdata, handles) 18. %resets the GUI by clearing all relevant fields
19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32.
handles.processDataCompleted = 0; %clears the axes cla(handles.axes1,'reset'); %set the popupmenu to default value set(handles.plot_popupmenu,'Value',1); %clears the contents of the listbox set(handles.inputFiles_listbox,'String',''); set(handles.inputFiles_listbox,'Value',0); %updates the handles structure guidata(hObject, handles);
Introduction
This tutorial will show you how to create a confirmation window when you attempt to close the GUI. Sometimes, the user might accidentally close the GUI window without meaning to. To protect against this, we can implement a confirmation window that appears when the user attempts to close the GUI. This feature can protect the user from losing any work that has been performed.
This tutorial is written for those with some experience creating a Matlab GUI. If youre new to creating GUIs in Matlab, you should visit this tutorial first. Basic knowledge of Matlab is highly recommended. Matlab version 2007a is used in writing this tutorial. Both earlier versions and new versions should be compatible as well (as long as it isant too outdated). Lets get started!
This line is of code is telling matlab to run the closeGUI function once someone attempts to close the GUI figure. Make sure to put this line of code before the line
guidata(hObject, handles);
2. Next, copy this code at the end of the GUI .m file OR in a separate .m file, but make sure its saved in the same directory where the GUI files are located.
3. function closeGUI(src,evnt) 4. %src is the handle of the object generating the callback (the source of the event) 5. %evnt is the The event data structure (can be empty for some callbacks) 6. selection = questdlg('Do you want to close the GUI?',... 7. 'Close Request Function',... 8. 'Yes','No','Yes'); 9. switch selection, 10. case 'Yes', 11. delete(gcf) 12. case 'No' 13. return end
Notice that instead of the @, we use the single quotation marks. By using the single quotation marks, you are assigning a separate m-file to the close request function of the figure. Make sure to put this line of code before the line
guidata(hObject, handles);
2. Next, copy this code into a separate .m file, but make sure its saved in the same directory where the GUI files are located. Notice that we did not define any input arguments here.
3. function closeGUI 4. 5. selection = questdlg('Do you want to close the GUI?',... 6. 'Close Request Function',... 7. 'Yes','No','Yes'); 8. switch selection, 9. case 'Yes', 10. delete(gcf) 11. case 'No' 12. return end
And thats it!!! Pretty simple like I mentioned. If you want to see an example, go ahead to the next page.
3. Choose to open the sample GUI by clicking on Open Existing GUI. Click on Browse to locate where you saved the GUI files.
4. Here is what the GUI should look like when you open it:
5. Click on the icon on the GUI figure to bring up the accompanying .m file. 6. Add the following code to the opening function, closeGUI_tutorial_OpeningFcn
set(handles.figure1,'CloseRequestFcn',@closeGUI);
Make sure to add the above line of code before this line of code:
guidata(hObject, handles);
7. Now, add the following code to the end of the GUI m-file.
8. function closeGUI(src,evnt) 9. %this function is called when the user attempts to close the GUI window 10. 11. %this command brings up the close window dialog 12. selection = questdlg('Do you want to close the GUI?',... 13. 'Close Request Function',... 14. 'Yes','No','Yes'); 15. 16. %if user clicks yes, the GUI window is closed, otherwise 17. %nothing happens 18. switch selection, 19. case 'Yes', 20. delete(gcf) 21. case 'No' 22. return end
23. Now, save your .m file and run the GUI. You should see the following GUI appear
24. Now, try to close the GUI. You should see the following appear:
25. Click yes to close the GUI, or click no to keep it open. This is the end of the tutorial.
Contents
Adding a Table to Your GUI using GUIDE Displaying Data on the Table Adding Column and Row Labels Modifying your Table through the m-file Next Time Links and Downloads
A neat feature is that the table is smart enough to fill out the table according to the size of the data matrix that you feed it. So if I had done the following instead:
myData = rand(100,100);
The table would incorporate the use of scroll bars, as shown below.
Now, if you click on any of the fields in the above picture, it will bring up the Table Property Editor. This is where you can add Row and Column labels. For example:
Make sure you click on the Rows, and that you select the Show names entered below as the row headers option. Finally, you just need to modify the names. Similarly, you can do the same for the columns.
Once youre done with that. you should see the following:
And once you run your GUI, you can see the final result. A well labeled table that displays your data beautifully!
%store the row headers into a cell array rowHeaders = {'Blink','Dagger','Loves','MATLAB','!!!!!!'}; %set the row labels set(handles.uitable1,'RowName',rowHeaders); %do the same for the column headers columnHeaders = {'Quan','Daniel','Rob','Zane'}; set(handles.uitable1,'ColumnName',columnHeaders);
In this example, we assumed that we knew the dimensions of our table. If you dont know the size of your table beforehand, then it can be difficult to apply data labels that are meaningful. By working through the m-file, you obtain more flexibility since you wont have to go back and modify the .fig file every time you want to make a change. And if you are going to apply dynamic labeling, then working from the m-file is going to be much easier.
Contents
Accessing Table Data within GUI Callbacks Accessing only the Selected Data
For simplicity sake, lets assume that you would like to create a button that will add 3 to each of the entries of the table when the button is pressed. How would you go about doing this? Its actually quite straightforward. Lets take a look at the callback for the add button:
function add_pushbutton_Callback(hObject, eventdata, handles) % hObject handle to add_pushbutton (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) %get the table data tableData = get(handles.uitable1,'data'); %add 3 to the table tableData = tableData + 3; %update the table set(handles.uitable1,'data',tableData);
So now, when I press the Add 3 button, it adds 3 to the table! This is just a simple example to show how to extract the data from the UITABLE, and to perform an operation on it.
How would you go about doing this? Read on and all will be revealed.
Enabling CellSelectionCallback
The first thing we need to do is to enable the Cell Selection Callback. But first, why are we doing this? Enabling this callback will allow us to keep track of what cells are being selected on the table. You can do this by bringing up the Property Inspector for the UITABLE, and then clicking the following icon as shown in the image below.
If you did it correctly, your m-file should have been updated to include the following:
% --- Executes when selected cell(s) is changed in uitable1. function uitable1_CellSelectionCallback(hObject, eventdata, handles) % hObject handle to uitable1 (see GCBO) % eventdata structure with the following fields (see UITABLE) % Indices: row and column indices of the cell(s) currently selecteds % handles structure with handles and user data (see GUIDATA)
In addition, the CellSelectionCallback field should now be populated as shown in the image below:
Now, we need to write the callback for the button we just added:
% --- Executes on button press in sumNumbers_pushbutton. function sumNumbers_pushbutton_Callback(hObject, eventdata, handles) % hObject handle to sumNumbers_pushbutton (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) %get the number of rows and columns [rows,columns] = size(handles.selectedCells); %get the data from the UITABLE tableData = get(handles.uitable1,'data'); %initialize the sum sum = 0; %loop through each selected cell and keep a running sum
%can anyone thing of a better way to do this? for x=1:rows sum = sum +tableData(tableIndices(x,1),tableIndices(x,2)); end %display the sum on the GUI set(handles.sum_text,'String',num2str(sum))
And there you have it, now you can select any number of cells, and then sum up the value of the contents!
Contents
About Zanes GUI: As a tutor, I have to work at one of three offices during the week (Federal Way, Issaquah, Lynnwood). One of my worries working for this company was the price of gas because driving would take up a good part of my pay. I heard that gas for work could be deducted from taxes so I decided to keep track of all of my miles. I had recently completed some of the blinkdagger tutorials and thought a GUI would be a great project to practice my new skills. I could have just made a simple Excel sheet, but then I would have to eventually come back and add everything up manually, why not have it all completed for me!
Adding in Panels
The panel icon on the GUIDE editor looks like this: You can find it here:
As can be seen, the panels immediately give the GUI a better structure and organization. Panels are an effective way to group together common elements and are simple to use. They provide lines and boundaries and makes it easier for the end user to navigate the GUI easily.
Labeling Panels
In this part of the revamping, we have simply given the panels a title name to identify that particular panel. In addition, we removed the redundant headers. This helps clean up the GUI even more and removes extraneous information. Also, the panel sizes are reduced to a size that adequately frames the content as to not waste space.
Reorganization
After some reorganization of the GUI, there is extra space that is no longer needed. This reduces the GUI size yet again and makes it more compact. In addition, the Input Data panel was extended to include the rest of the buttons so that there wouldnt be any elements that are orphaned and just dangling alone.
Final Thoughts
Panels are an effective way to separate your GUI into manageable sections, giving the GUI definition. Join us next time as we continue to transform Zanes GUI from the ugly duckling to the beautiful swan.
discuss how to implement a help feature for your GUI. The help feature should be informative and simple so that any person can learn how to use the GUI quickly. The help feature should offer other useful information such as the version number, author, and other tidbits of information. In the last post, we ended up with this GUI, but we never did anything for the help portion of the GUI:
Contents
How to use this GUI: Quick User Guide About this GUI Putting it all Together
When the GUI is called by the user, the user will see the following:
Of course, you can add more tidbits and information depending on the actual GUI. You might also want to add a FAQ section here if you feel your GUI needs some extra explanation. If you cant fit all the information into one screen, you can also utilize pushbuttons that will change the static text when the button is pressed.
When the GUI is called by the user, the user will see the following:
the callback for the How to use this GUI menu item is:
function helpGUI_Callback(hObject, eventdata, handles) %this is the name of the help GUI that contains the help information helpGUI_description
function aboutGui_Callback(hObject, eventdata, handles) %this command calls the GUI that has the "about" information aboutGUI_description
Addpath
I have found that the addpath command within MATLAB can really help you keep your files organized. If your GUI utilizes other m-files or images, it can be advantageous to store them in appropriate subdirectories. you can store them in a separate directory and simply add the path of that directory. It can get a little crowded in the main directory of your GUI, so its usually a good idea to put all your sub functions and sub GUIs in a lower level folder. For example, the following image shows how I originally had my m-files organized. The main GUI files are Tutor_Commuter_QQ_05.fig and Tutor_Commuter_QQ_05.m. The rest of the files are used in creating the help.
In the following image, you can see how I reorganized the files. All the files except the main files are placed in the subfunctions folder.
Now, all that has to done is to add the path of the subfunctions within the Opening Function of the main GUI.
%the addpath adds the subfunctions folder onto the MATLAB path. m-files %within this directory will be run as if it were in the same directory as the %main GUI itself %the fullfile command combines directory names and parts %pwd is the "present working directory", which in this case will be where %the main GUI is located addpath(fullfile(pwd,'subfunctions');
Next Time
It looks like weve revamped Zanes GUI so that its obtained a shade of respectability. If you guys have any more ideas on what can be done, wed love to hear about it. In the near future, well open up the floor and ask for GUIs from our readers! Until next time . . .