Click here to Skip to main content
15,895,709 members
Articles / Web Development / ASP.NET

Race to Linux - Race 2: Time Tracker Starter Kit

Rate me:
Please Sign up or sign in to vote.
4.25/5 (5 votes)
26 Sep 20051 min read 40.4K   274   16  
Time Tracker Starter Kit port to Linux using Mono and FireBird
using System;
using System.Collections;
using System.Data;
using System.Text;
using System.Web.UI.WebControls;
using ASPNET.StarterKit.TimeTracker.BusinessLogicLayer;

namespace ASPNET.StarterKit.TimeTracker.Web
{
	//*********************************************************
	//
	// Main page of the Application, for users to enter time entries.
	//
	//*********************************************************

	public class TimeEntry : System.Web.UI.Page
	{
		//Use Time Entry component to capture user's input during edit mode
		protected BusinessLogicLayer.TimeEntry	_userInput = new BusinessLogicLayer.TimeEntry(0, 0, 0, 0, DateTime.MinValue, null, -1M);
		protected DataTable						_dayListTable = null;

		protected System.Web.UI.WebControls.Button					AddEntry;
		protected System.Web.UI.WebControls.CompareValidator		CompareValidator1;
		protected System.Web.UI.WebControls.Button					Cancel;
		protected System.Web.UI.WebControls.DropDownList			CategoryList;
		protected System.Web.UI.WebControls.DropDownList			Days;
		protected System.Web.UI.WebControls.TextBox					Description;
		protected System.Web.UI.WebControls.TextBox					Hours;
		protected System.Web.UI.WebControls.DropDownList			ProjectList;
		protected System.Web.UI.WebControls.RangeValidator			RangeValidator1;
		protected System.Web.UI.WebControls.RequiredFieldValidator	Requiredfieldvalidator1;
		protected System.Web.UI.WebControls.RequiredFieldValidator	RequiredFieldValidator2;
		protected System.Web.UI.WebControls.DataGrid				TimeEntryGrid;
		protected System.Web.UI.WebControls.Image					TimeGraph;
		protected System.Web.UI.WebControls.DropDownList			UserList;
		protected System.Web.UI.WebControls.TextBox					WeekEnding;
		
		private TTUser	 _user;
		private DateTime _weekEndingDate = DateTime.Today;
		private DateTime _weekStartingDate = DateTime.Today;

		//*******************************************************
		//
		// The Page_Load server event handler on this page is used
		// to populate information fields on this page
		//
		//*******************************************************

		private void Page_Load(object sender, System.EventArgs e)
		{
			_user = new TTUser(
				TTSecurity.GetUserID(), 
				User.Identity.Name, 
				TTSecurity.GetName(), 
				TTSecurity.GetUserRole());

			if (!Page.IsPostBack)
			{
				// If page is loaded for the first time, select the current week.
				BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(DateTime.Today, 
					ref _weekStartingDate, ref _weekEndingDate);
				WeekEnding.Text = _weekEndingDate.ToShortDateString();

				BindUserList();
				BindEntryFields();
				BindTimeSheet(_user.UserID, _weekStartingDate, _weekEndingDate);
			}
			else
			{
				// Selects the weekending date, corresponding to week user selected.
				BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(Convert.ToDateTime(WeekEnding.Text), 
					ref _weekStartingDate, ref _weekEndingDate);
				_dayListTable = BusinessLogicLayer.TimeEntry.GetWeek(Convert.ToDateTime(WeekEnding.Text));
			}
		}
		
		//*******************************************************
		//
		// BindCategoryList method databinds data to CategoryList dropdown.
		//
		//*******************************************************

		private void BindCategoryList()
		{
			if (ProjectList.SelectedItem != null) 
			{
				// CategoryList is different for each project, Project.GetCategories gets a list of 
				// categories based on the project.
				CategoryList.DataSource = Project.GetCategories(Convert.ToInt32(ProjectList.SelectedItem.Value));
				CategoryList.DataValueField = "CategoryID";
				CategoryList.DataTextField = "Abbreviation";
				CategoryList.DataBind();
			}
		}

		//*******************************************************
		//
		// BindDates method populates the DropDownList Days to show each day of the week
		//
		//*******************************************************

		private void BindDates()
		{
			_dayListTable = BusinessLogicLayer.TimeEntry.GetWeek(Convert.ToDateTime(WeekEnding.Text));
			Days.DataSource = _dayListTable;
			Days.DataValueField = "Date";
			Days.DataTextField = "Day";
			Days.DataBind();
			Days.Items.FindByText(Convert.ToDateTime(DateTime.Today).ToString("ddd")).Selected= true;
		}

		//*******************************************************
		//
		// BindEntryFields calls methods that populate the drop down lists with data.
		//
		//*******************************************************

		private void BindEntryFields()
		{
			BindDates();
			BindProjectList();
			BindCategoryList();
		}
		
		//*******************************************************
		//
		// BindProjectList method populates the DropDownList ProjectList to show
		// list of projects that belongs to the user.
		//
		//*******************************************************

		private void BindProjectList()
		{
			// Preserve the selected value so information is still there after postback.
			if (ProjectList.SelectedItem != null)
				_userInput.ProjectID = Convert.ToInt32(ProjectList.SelectedItem.Value);

			ProjectList.DataSource = ListUserProjects();
			ProjectList.DataTextField = "Name";
			ProjectList.DataValueField = "ProjectID";
			ProjectList.DataBind();

			// Select the correct item from ProjectList.
			if (ProjectList.Items.FindByValue(_userInput.ProjectID.ToString()) != null)
				ProjectList.Items.FindByValue(_userInput.ProjectID.ToString()).Selected = true;
		}

		
		//*******************************************************
		//
		// An overload of BindTimeSheet method
		// this is called when the logged on user in querying time entry about themself.
		//
		//*******************************************************

		private void BindTimeSheet(int userID, DateTime start, DateTime end)
		{
			BindTimeSheet(userID, userID, start, end);
		}

		//*******************************************************
		//
		// BindTimeSheet method databinds the TimeEntryGrid based on the user
		// currently logged in and user which is being queried.
		//
		//*******************************************************

		private void BindTimeSheet(int queryUserID, int userID, DateTime start, DateTime end)
		{
			TimeEntriesCollection entryList = BusinessLogicLayer.TimeEntry.GetEntries(queryUserID, userID, start, end);

			// Sort datagrid if it is not empty.
			if (entryList != null) 
			{
				SortGridData(entryList, SortField, SortAscending);
			}
			// Draw time graph with new dataset.
			DrawTimeGraph(entryList, _dayListTable);

			TimeEntryGrid.DataSource = entryList;
			TimeEntryGrid.DataBind();
			
		}

		//*******************************************************
		//
		// BindUserList method databinds a list of users to the UserList;
		// This is based on the user currently logged in.
		//
		//*******************************************************

		private void BindUserList()
		{
			UserList.DataSource = TTUser.GetUsers(_user.UserID, _user.Role);
			UserList.DataBind();

			//Select User Drop Down to the correct value.
			UserList.Items.FindByValue(_user.UserID.ToString()).Selected=true;
		}

		//*******************************************************
		//
		// ClearEntryFields method clears all the entry fields on this page.
		//
		//*******************************************************

		private void ClearEntryFields()
		{
			BindEntryFields();

			// Make sure that no items appear in edit mode in the TimeEntryGrid.
			TimeEntryGrid.EditItemIndex = -1;

			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
			Description.Text = string.Empty;
			Hours.Text = string.Empty;
		}
	
		//*******************************************************
		//
		// DrawTimeGraph method creates the bar graph on the fly.
		// Binds accordingly to number hours in each day of the week.
		//
		//*******************************************************

		private void DrawTimeGraph(TimeEntriesCollection chartData, DataTable dtWeek)
		{
			// Assume dtWeek is already in the correct order according to FirstDayOfWeek Setting.
			Hashtable htWeekSummary = new Hashtable(7);	//7 days of data - key: EntryDate | value: Duration.

			// x & y values holder
			StringBuilder xValues = new StringBuilder();
			StringBuilder yValues = new StringBuilder();

			//Initialize htWeekSummary to start with first day of week setting.
			foreach (DataRow row in dtWeek.Rows)
			{
				htWeekSummary.Add(Convert.ToDateTime(row["Date"]).ToShortDateString(), 0m);
			}

			// Summarize data from grid Group By Days of Week.
			foreach (BusinessLogicLayer.TimeEntry time in chartData)
			{
				string key = time.EntryDate.ToShortDateString();
				Decimal dayDuration = Convert.ToDecimal(htWeekSummary[key]);
				dayDuration += time.Duration;
				htWeekSummary[key] = dayDuration;
			}

			//Build x & y values for graph.
			int index = 1;
			foreach (DataRow row in dtWeek.Rows)
			{
				string key = Convert.ToDateTime(row["Date"]).ToShortDateString();
				string currentDay = Convert.ToDateTime(key).ToString("ddd");
				string currentHour = Convert.ToString(htWeekSummary[key]);

				xValues.Append(currentDay);
				yValues.Append(currentHour);

				//Don't append separator if this is the last item.
				if (index != dtWeek.Rows.Count)
				{
					xValues.Append("|");
					yValues.Append("|");
				}
				index++;
			}

			//Attach image generator to image.
			TimeGraph.ImageUrl = "TimeEntryBarChart.aspx?" + 
				"xValues=" + xValues.ToString() + 
				"&yValues=" + yValues.ToString();
		}
		
		//*******************************************************
		//
		// ListGridCategories method returns a List of categories based on the projectID.
		//
		//*******************************************************

		protected CategoriesCollection ListGridCategories(int projectID)
		{
			return Project.GetCategories(projectID);
		}

		//*******************************************************
		//
		// ListUserProjects method returns a ArrayList of available projects.
		//
		//*******************************************************

		protected ProjectsCollection ListUserProjects()
		{
			return BusinessLogicLayer.Project.GetProjects(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value));
		}

		//*******************************************************
		//
		// SortGridData methods sorts the TimeEntryGrid based on which
		// sort field is being selected.  Also does reverse sorting based on the boolean.
		//
		//*******************************************************

		private void SortGridData(TimeEntriesCollection list, string sortField, bool asc)
		{
			TimeEntriesCollection.TimeEntryFields sortCol = TimeEntriesCollection.TimeEntryFields.InitValue;

			switch(sortField)
			{
				case "EntryDate":
					sortCol = TimeEntriesCollection.TimeEntryFields.Day;
					break;
				case "ProjectName":
					sortCol = TimeEntriesCollection.TimeEntryFields.Project;
					break;
				case "CategoryName":
					sortCol = TimeEntriesCollection.TimeEntryFields.Category;
					break;
				case "Duration":
					sortCol = TimeEntriesCollection.TimeEntryFields.Hours;
					break;
				case "Description":
					sortCol = TimeEntriesCollection.TimeEntryFields.Description;
					break;
				default:
					break;
			}

			list.Sort(sortCol, asc);
		}

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		///	<summary>
		///	Required method for Designer support - do not modify
		///	the contents of this method with the code editor.
		///	</summary>
		private void InitializeComponent()
		{    
			this.WeekEnding.TextChanged += new System.EventHandler(this.WeekEnding_TextChanged);
			this.ProjectList.SelectedIndexChanged += new System.EventHandler(this.ProjectList_SelectedIndexChanged);
			this.AddEntry.Click += new System.EventHandler(this.AddEntry_Click);
			this.Cancel.Click += new System.EventHandler(this.Cancel_Click);
			this.UserList.SelectedIndexChanged += new System.EventHandler(this.UserList_OnChange);
			this.TimeEntryGrid.CancelCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.TimeEntryGrid_OnCancel);
			this.TimeEntryGrid.EditCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.TimeEntryGrid_OnEdit);
			this.TimeEntryGrid.SortCommand += new System.Web.UI.WebControls.DataGridSortCommandEventHandler(this.TimeEntryGrid_Sort);
			this.TimeEntryGrid.UpdateCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.TimeEntryGrid_OnUpdate);
			this.TimeEntryGrid.DeleteCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.TimeEntryGrid_OnDelete);
			this.TimeEntryGrid.ItemDataBound += new System.Web.UI.WebControls.DataGridItemEventHandler(this.TimeEntryGrid_Itembound);
			this.Load += new System.EventHandler(this.Page_Load);

		}
		#endregion
		
		//*******************************************************
		//
		// AddEntry_Click server event handler on this page is used to 
		// saves a time entry and adds it to the system.
		//
		//*******************************************************

		private void AddEntry_Click(object sender, System.EventArgs e)
		{
			// check validation.
			Requiredfieldvalidator1.Validate();
			RequiredFieldValidator2.Validate();
			RangeValidator1.Validate();
			CompareValidator1.Validate();
			
			// proceeds if data is valid then create an obejct and save it.
			if (Requiredfieldvalidator1.IsValid && RequiredFieldValidator2.IsValid && CompareValidator1.IsValid && RangeValidator1.IsValid) 
			{
				BusinessLogicLayer.TimeEntry te = new BusinessLogicLayer.TimeEntry(0, 
					Convert.ToInt32(UserList.SelectedItem.Value), 
					Convert.ToInt32(ProjectList.SelectedItem.Value),
					Convert.ToInt32(CategoryList.SelectedItem.Value), 
					Convert.ToDateTime(Days.SelectedItem.Value), 
					TTSecurity.CleanStringRegex(Description.Text), Convert.ToDecimal(Hours.Text));
				te.Save(); 
				
				ClearEntryFields();
			}
		}	
		
		//*******************************************************
		//
		// Cancel_Click server event handler on this page is used to
		// clear all entry fields on page.
		//
		//*******************************************************

		private void Cancel_Click(object sender, System.EventArgs e)
		{
			ClearEntryFields();
		}

		//*******************************************************
		//
		// ProjectList_SelectedIndexChanged server event handler on this page is used to 
		// update the CategoryList dropdown when a different project is selected
		//
		//*******************************************************

		private void ProjectList_SelectedIndexChanged(object sender, System.EventArgs e)
		{
			BindCategoryList();
		}

		//*******************************************************
		//
		// TimeEntryGrid_Itembound server event handler on this page is used
		// during TimeEntryGrid databind
		//
		//*******************************************************

		private void TimeEntryGrid_Itembound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
		{
			// During in-line-editing mode this selects corresponding items for dropdowns and text boxes.
			// The _userInput object is created in TimeEntryGrid_OnEdit().
			if (e.Item.ItemType == ListItemType.EditItem) 
			{
				DropDownList currentCbo = (DropDownList) e.Item.FindControl("EntryDays");
				currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.Day));

				currentCbo = (DropDownList) e.Item.FindControl("EntryProjects");
				currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.ProjectName));

				currentCbo = (DropDownList) e.Item.FindControl("EntryCategories");
				currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.CategoryName));				
			}
		}

		//*******************************************************
		//
		// TimeEntryGrid_OnCancel server event handler on this page is used to 
		// quit in-line-editing mode. and Undo all changes
		//
		//*******************************************************

		protected void TimeEntryGrid_OnCancel(Object sender, DataGridCommandEventArgs e)
		{
			TimeEntryGrid.EditItemIndex = -1;
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}
		
		//*******************************************************
		//
		// TimeEntryGrid_OnDelete server event handler on this page is used to 
		// delete a item from the database.
		//
		//*******************************************************

		protected void TimeEntryGrid_OnDelete(Object sender, DataGridCommandEventArgs e)
		{
			int entryLogID = Convert.ToInt32(TimeEntryGrid.DataKeys[(int)e.Item.ItemIndex]);
			BusinessLogicLayer.TimeEntry.Remove(entryLogID);
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}

		//*******************************************************
		//
		// TimeEntryGrid_OnEdit server event handler on this page is used to
		// start in-line-editing mode for TimeEntryGrid.
		//
		//*******************************************************

		protected void TimeEntryGrid_OnEdit(Object sender, DataGridCommandEventArgs e)
		{
			TimeEntryGrid.EditItemIndex = e.Item.ItemIndex;

			// Saves current info on temporary, use later in TimeEntryGrid_Itembound()
			_userInput.Day = ((Label) e.Item.FindControl("EntryDay")).Text;
			_userInput.ProjectName = ((Label) e.Item.FindControl("EntryProject")).Text.Trim();
			_userInput.ProjectID = Convert.ToInt32(((Label) e.Item.FindControl("EntryProjectID")).Text);
			_userInput.CategoryName = ((Label) e.Item.FindControl("EntryCategory")).Text.Trim();
			_userInput.Duration = Convert.ToDecimal(((Label) e.Item.FindControl("EntryDuration")).Text);
			_userInput.Description = ((Label) e.Item.FindControl("GridDescription")).Text.Trim();
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}

		//*******************************************************
		//
		// TimeEntryGrid_OnUpdate server event handler on this page is used to
		// update the changes made from in-line-editing.
		//
		//*******************************************************

		protected void TimeEntryGrid_OnUpdate(Object sender, DataGridCommandEventArgs e)
		{
			DataGridItem item = TimeEntryGrid.Items[TimeEntryGrid.EditItemIndex];

			RequiredFieldValidator required = (RequiredFieldValidator) item.FindControl("RequiredFieldValidatorGridHours");
			CompareValidator comparer = (CompareValidator) item.FindControl("CompareValidatorGridHours");

			required.Validate();
			comparer.Validate();
			
			// Check for validation, if all valid, update the object to data entered and save it.
			if (required.IsValid && comparer.IsValid) 
			{
				int entryLogID;
				int userID;
				int projectID;
				int categoryID;
				DateTime taskDate;
				string description;
				decimal duration;
				BusinessLogicLayer.TimeEntry te = null;

				// Fill in properties for TimeEntry object.
				entryLogID = Convert.ToInt32(TimeEntryGrid.DataKeys[(int)e.Item.ItemIndex]);
				userID = Convert.ToInt32(UserList.SelectedItem.Value);
				projectID = Convert.ToInt32(((DropDownList) e.Item.FindControl("EntryProjects")).SelectedItem.Value);
				categoryID = Convert.ToInt32(((DropDownList) e.Item.FindControl("EntryCategories")).SelectedItem.Value);
				taskDate = Convert.ToDateTime(((DropDownList) e.Item.FindControl("EntryDays")).SelectedItem.Value);
				description = TTSecurity.CleanStringRegex(((TextBox) e.Item.FindControl("EntryDescription")).Text);
				duration = Convert.ToDecimal(((TextBox) e.Item.FindControl("EntryHours")).Text);

				// Save the TimeEntry object.
				te = new BusinessLogicLayer.TimeEntry(entryLogID, userID, projectID, categoryID, taskDate, description, duration);
				te.Save();

				// Quit in-line-editing mode.
				TimeEntryGrid.EditItemIndex = -1;
				BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
			}
		}

		//*******************************************************
		//
		// TimeEntryGrid_Sort server event handler on this page is used to
		// sorts the TimeEntryGrid according to the sort field
		// being selected.
		//
		//*******************************************************
	
		private void TimeEntryGrid_Sort(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e)
		{
			TimeEntryGrid.EditItemIndex = -1;
			SortField = e.SortExpression;
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}

		//*******************************************************
		//
		// UserProjects_OnChange server event handler on this page is called when user makes
		// a seletion change from project dropdown within in-line-editing, is used to 
		// update the category dropdown items according to the project selected
		//
		//*******************************************************

		protected void UserProjects_OnChange(object sender, System.EventArgs e)
		{
			DataGridItem item = TimeEntryGrid.Items[TimeEntryGrid.EditItemIndex];
			
			// Save temporary inputs so that data would be still there when user makes a Project selection
			// and cause postback.
			if (item != null)
			{
				TextBox txt = (TextBox) item.FindControl("EntryHours");
				_userInput.Duration = Convert.ToDecimal(txt.Text);
				
				TextBox txtDesc = (TextBox) item.FindControl("EntryDescription");
				_userInput.Description = txtDesc.Text;

				DropDownList EntryDays = (DropDownList)item.FindControl("EntryDays");
				_userInput.EntryDate = Convert.ToDateTime(EntryDays.SelectedItem.Value);
				_userInput.Day = EntryDays.SelectedItem.Text;

			}
			DropDownList EntryProjects = (DropDownList) sender;
			_userInput.ProjectID = Convert.ToInt32(EntryProjects.SelectedItem.Value);
			_userInput.ProjectName = EntryProjects.SelectedItem.Text;
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}
		
		//*******************************************************
		//
		// UserList_OnChange server event handler on this page is used
		// when a selection is changed in UserList DropDown
		// UserList items are limited to Role of the current user logged on the system.
		//
		//*******************************************************

		private void UserList_OnChange(object sender, System.EventArgs e)
		{
			TimeEntryGrid.EditItemIndex = -1;
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
			BindEntryFields();
		}

		//*******************************************************
		//
		// WeekEnding_TextChanged server event handler on this page is used to
		// select the correct week starting date and week ending date
		// it gets called when user makes a selection change on calendar.
		//
		//*******************************************************

		private void WeekEnding_TextChanged(object sender, System.EventArgs e)
		{
			TimeEntryGrid.EditItemIndex = -1;
			BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(Convert.ToDateTime(WeekEnding.Text), 
				ref _weekStartingDate, ref _weekEndingDate);
			WeekEnding.Text = _weekEndingDate.ToShortDateString();
			BindEntryFields();
			BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate);
		}

		string SortField 
		{
			get 
			{
				object o = ViewState["SortField"];
				if (o == null) 
				{
					return String.Empty;
				}
				return (string)o;
			}

			set 
			{
				if (value == SortField) 
				{
					// same as current sort file, toggle sort direction
					SortAscending = !SortAscending;
				}
				ViewState["SortField"] = value;
			}
		}

		//*********************************************************************
		//
		// SortAscending property is tracked in ViewState
		//
		//*********************************************************************

		bool SortAscending 
		{
			get 
			{
				object o = ViewState["SortAscending"];
				if (o == null) 
				{
					return true;
				}
				return (bool)o;
			}

			set 
			{
				ViewState["SortAscending"] = value;
			}
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Uruguay Uruguay
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions