using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using CarManagerV3.Forms; namespace CarManagerV3 { public partial class MainForm : Form { List cars = new List(); string filepath = ""; public static MainForm GetMainForm() { // This is a singleton pattern to ensure only one instance of MainForm exists. // If you need to access the MainForm instance, you can use this method. return Application.OpenForms.OfType().FirstOrDefault() ?? new MainForm(); } public MainForm(string pathToOpen = "") { InitializeComponent(); if (Properties.Settings.Default.DataLocation == "") { Properties.Settings.Default.DataLocation = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\CarManagerV3"; Properties.Settings.Default.Save(); } // Open the most recent file if it exists. Otherwise, use default filepath. List recentFiles = SafeManager.GetRecentPaths(); if (!String.IsNullOrEmpty(pathToOpen)) { filepath = pathToOpen; SafeManager.AddRecentPath(filepath); } else if (recentFiles.Count > 0) { filepath = recentFiles[0]; } else { openWelcomeScreen(); } SafeManager.InitializeFile(filepath); StateManager.setFilePath(filepath); try { List _cars = SafeManager.ReadCars(filepath); cars = _cars; refreshCars(_cars, false); } catch (LegacyException) { Console.Error.WriteLine("Legacy file format detected. Prompting user to select a new file."); showOpenFileDialog(); } refreshRecents(); } public void openWelcomeScreen() { Welcome welcome = new Welcome(); // disable main form while welcome screen is open this.Enabled = false; welcome.OpenFileCallback = () => { showOpenFileDialog(); if (filepath != "") { welcome.Close(); this.Enabled = true; } }; welcome.NewFileCallback = () => { showSaveAsDialog(); if (filepath != "") { welcome.Close(); this.Enabled = true; } }; welcome.ShowDialog(); } public void showOpenFileDialog() { openToolStripMenuItem.PerformClick(); } public void showSaveAsDialog() { saveAsToolStripMenuItem.PerformClick(); } /// /// Refreshes the cars displayed in the flow layout panel. /// /// The cars. /// if set to true [update global]. private async void refreshCars(List _cars, bool updateGlobal = true, bool force = false) { this.Text = "Car Manager - " + System.IO.Path.GetFileName(filepath); // Sort by Car.Order. If equal, sort by ID _cars = _cars.Count > 0 ? _cars.OrderBy(c => c.Order).ToList() : _cars; if (updateGlobal) { cars = _cars; } foreach (Car car in _cars) { // not in list? add it bool isNew = flpCars.Controls.OfType().All(c => c.Car.Id != car.Id); // existing but changed? update it CarCard card = new CarCard(); if (!isNew) { CarCard existing = flpCars.Controls.OfType().First(c => c.Car.Id == car.Id); Car existingCar = existing.Car; if (existing == null) { Console.Error.WriteLine($"[L] Error: Existing car card not found for car ID: {car.Id}"); continue; } // compare details // Console.WriteLine($"[L] Checking car: {car.Id} | Car Color: {car.Color} | Ex Color: {existingCar.Color}"); if (existingCar.IsChanged(car) || force) { Console.WriteLine($"[L] Updating car: {car.Id}"); // changes card = existing; if(force) card.LoadImage(); // reload image if forced refresh } else { // no changes // Console.WriteLine($"[L] No changes for car: {car.Id}"); flpCars.Controls.SetChildIndex(existing, _cars.IndexOf(car)); continue; } } card.CarName = $"{car.Make} {car.Model}"; card.CarDetails = $"({car.Order}) {car.Year}, {car.Mileage} km, ${car.Price}"; card.Car = car.Clone(); card.LoadImage(); // clear existing event handlers to prevent multiple subscriptions card.ClearCardClickedHandlers(); card.CardClicked += (s, e) => { Console.WriteLine($"Card clicked: {car.Id}"); CarDetailsForm detailsForm = new CarDetailsForm(car); detailsForm.FormClosed += async (s2, e2) => { Console.WriteLine("Car details form closed."); // refresh cars Console.WriteLine("Refreshing cars..."); List __cars = await Task.Run(() => SafeManager.ReadCars(filepath)); if (tbxSearch.Text.Length > 0) { Console.WriteLine("Search box has text, applying search filter."); cars = __cars; searchList(tbxSearch.Text); return; } refreshCars(__cars); }; detailsForm.ShowDialog(); }; ContextMenuStrip cms = new ContextMenuStrip(); cms.Items.Add("Move up", null, (s, e) => { int order = car.Order; // find car with order just less than this one Car other = cars.Where(c => c.Order < order).OrderByDescending(c => c.Order).FirstOrDefault(); if (other != null) { Console.WriteLine($"Swapping order of {car.ToString()} ({car.Order}) and {other.ToString()} ({other.Order})"); int temp = car.Order; car.Order = other.Order; other.Order = temp; cars = StateManager.normalizeOrders(cars); SafeManager.SaveCars(filepath, cars); refreshCars(cars); } }); cms.Items.Add("Move down", null, (s, e) => { int order = car.Order; // find car with order just greater than this one Car other = cars.Where(c => c.Order > order).OrderBy(c => c.Order).FirstOrDefault(); if (other != null) { Console.WriteLine($"Swapping order of {car.ToString()} ({car.Order}) and {other.ToString()} ({other.Order})"); int temp = car.Order; car.Order = other.Order; other.Order = temp; cars = StateManager.normalizeOrders(cars); SafeManager.SaveCars(filepath, cars); refreshCars(cars); } }); card.ContextMenuStrip = cms; if (isNew) { flpCars.Controls.Add(card); } flpCars.Controls.SetChildIndex(card, _cars.IndexOf(car)); } // Remove cards that are no longer in _cars var carIds = _cars.Select(c => c.Id).ToList(); var cardsToRemove = flpCars.Controls.OfType().Where(c => !carIds.Contains(c.Car.Id)).ToList(); foreach (var card in cardsToRemove) { flpCars.Controls.Remove(card); } flpCars.Refresh(); flpCars.Invalidate(); flpCars.Update(); } private void btnNewCar_Click(object sender, EventArgs e) { Car foocar = new Car("0", "New", "Car", 2020, "White", 0, 20000); CarDetailsForm detailsForm = new CarDetailsForm(foocar); detailsForm.FormClosed += (s2, e2) => { // refresh cars Console.WriteLine("Refreshing cars..."); List cars_ = SafeManager.ReadCars(filepath); refreshCars(cars_, false); }; detailsForm.ShowDialog(); } /// /// Filters the cars by a search query. /// /// The search query. /// /// A list of objects that match the query. /// List filterCarsByQuery(string query) { List results = new List(); foreach (Car car in cars) { if (car.Make.ToLower().Contains(query.ToLower()) || car.Model.ToLower().Contains(query.ToLower()) || car.Year.ToString().Contains(query) || car.Mileage.ToString().Contains(query) || car.Price.ToString().Contains(query)) { results.Add(car); } } return results; } /// /// Searches the list of cars by a query and refreshes the display. /// /// The query. void searchList(string query) { List results = filterCarsByQuery(query); refreshCars(results, false); } private async void tbxSearch_TextChanged(object sender, EventArgs e) { string query = tbxSearch.Text; await Task.Delay(100); // debounce if (query != tbxSearch.Text) return; // text changed during delay //flpCars.Controls.Clear(); if (string.IsNullOrWhiteSpace(query)) { refreshCars(cars); } else { searchList(query); } } private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e) { } private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog dlgOpen = new OpenFileDialog(); dlgOpen.Filter = "Compatible Files (*.csv;*.cars)|*.csv;*.cars|CSV Files (*.csv)|*.csv|Car Manager CSV (*.cars)|*.cars|All Files (*.*)|*.*"; dlgOpen.Title = "Open Car Data File"; // Default to users documents dlgOpen.InitialDirectory = SafeManager.getRecentFolder(); DialogResult result = dlgOpen.ShowDialog(); if (result == DialogResult.OK) { try { List importedCars = SafeManager.ReadCars(dlgOpen.FileName); if (importedCars.Count == 0) { throw new Exception("File doesn't contain valid Cars."); } filepath = dlgOpen.FileName; cars = importedCars; StateManager.setFilePath(filepath); // Refresh display refreshCars(cars); MessageBox.Show("File loaded successfully.", "Load File", MessageBoxButtons.OK, MessageBoxIcon.Information); SafeManager.AddRecentPath(filepath); refreshRecents(); } catch (LegacyException) { showOpenFileDialog(); } catch (Exception ex) { MessageBox.Show("Error loading file: " + ex.Message); } } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { refreshCars(cars); SafeManager.SaveCars(filepath, cars); MessageBox.Show("File saved successfully.", "Save File", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog dlgSave = new SaveFileDialog(); dlgSave.Filter = "Compatible Files (*.csv;*.cars)|*.csv;*.cars|CSV Files (*.csv)|*.csv|Car Manager CSV (*.cars)|*.cars|All Files (*.*)|*.*"; dlgSave.Title = "Save Car Data File As"; // Default to users documents dlgSave.InitialDirectory = SafeManager.getRecentFolder(); DialogResult result = dlgSave.ShowDialog(); if (result == DialogResult.OK) { // does file already exist? /*if (System.IO.File.Exists(dlgSave.FileName)) { var overwriteResult = MessageBox.Show("File already exists. Overwrite?", "Overwrite File", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (overwriteResult != DialogResult.Yes) { return; } }*/ // Windows already handles this lmao filepath = dlgSave.FileName; this.Text = "Car Manager - " + System.IO.Path.GetFileName(filepath); StateManager.setFilePath(filepath); SafeManager.SaveCars(filepath, cars); SafeManager.AddRecentPath(filepath); } refreshRecents(); } private void importToolStripMenuItem_Click(object sender, EventArgs e) { DialogResult result = MessageBox.Show("Importing will add cars from another file to this file. This action cannot be undone. Continue?", "Import Cars", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.Yes) { OpenFileDialog dlgOpen = new OpenFileDialog(); dlgOpen.Filter = "Compatible Files (*.csv;*.cars)|*.csv;*.cars|CSV Files (*.csv)|*.csv|Car Manager CSV (*.cars)|*.cars|All Files (*.*)|*.*"; dlgOpen.Title = "Import Car Data File"; // Default to users documents dlgOpen.InitialDirectory = SafeManager.getRecentFolder(); DialogResult dlgResult = dlgOpen.ShowDialog(); if (dlgResult == DialogResult.OK) { try { Console.WriteLine("Starting merge..."); List importedCars = SafeManager.ReadCars(dlgOpen.FileName); if (importedCars.Count == 0) { throw new Exception("File doesn't contain valid Cars."); } // merge cars foreach (Car car in importedCars) { cars.Add(car); } DialogResult mergeAsNewFileResult = MessageBox.Show("Do you want to save the merged cars as a new file?", "Save As New File", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (mergeAsNewFileResult == DialogResult.Yes) { SaveFileDialog dlgSave = new SaveFileDialog(); dlgSave.Filter = "Compatible Files (*.csv;*.cars)|*.csv;*.cars|CSV Files (*.csv)|*.csv|Car Manager CSV (*.cars)|*.cars|All Files (*.*)|*.*"; dlgSave.Title = "Save Merged Car Data File As"; // Default to users documents dlgSave.InitialDirectory = SafeManager.getRecentFolder(); DialogResult saveResult = dlgSave.ShowDialog(); if (saveResult == DialogResult.OK) { filepath = dlgSave.FileName; StateManager.setFilePath(filepath); SafeManager.SaveCars(filepath, cars); SafeManager.AddRecentPath(filepath); refreshRecents(); } } else { // save to current file SafeManager.SaveCars(filepath, cars); } // Refresh display refreshCars(cars); MessageBox.Show("File imported successfully.", "Import File", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (LegacyException) { MessageBox.Show("The file you are trying to import is in a legacy format that is no longer supported. Please convert the file to the new format and try again.", "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show("Error importing file: " + ex.Message); } } else { Console.WriteLine("Import cancelled."); } } } // TODO: Unbind and remove this. private void recentFilesToolStripMenuItem_Click(object sender, EventArgs e) { } /// /// Refreshes the recently opened files menu. /// private void refreshRecents() { recentFilesToolStripMenuItem.DropDownItems.Clear(); List recentFiles = SafeManager.GetRecentPaths(); recentFilesToolStripMenuItem.Enabled = recentFiles.Count > 0; recentFilesToolStripMenuItem.ToolTipText = recentFiles.Count > 0 ? "" : "No recent files."; foreach (string path in recentFiles) { ToolStripMenuItem item = new ToolStripMenuItem(path); item.Click += (s, e2) => { try { List importedCars = SafeManager.ReadCars(path); if (importedCars.Count == 0) { throw new Exception("File doesn't contain valid Cars."); } filepath = path; cars = importedCars; StateManager.setFilePath(filepath); // Refresh display refreshCars(cars); MessageBox.Show("File loaded successfully.", "Load File", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (LegacyException) { MessageBox.Show("The file you are trying to open is in a legacy format that is no longer supported. Please convert the file to the new format and try again.", "Load Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show("Error loading file: " + ex.Message); } }; recentFilesToolStripMenuItem.DropDownItems.Add(item); } } private void revealInFileExplorerToolStripMenuItem_Click(object sender, EventArgs e) { // Open File Explorer at the location of the current filepath if (System.IO.File.Exists(filepath)) { System.Diagnostics.Process.Start("explorer.exe", "/select,\"" + filepath + "\""); } else { MessageBox.Show("File does not exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void openWelcomeScreenToolStripMenuItem_Click(object sender, EventArgs e) { openWelcomeScreen(); } private void addCarToolStripMenuItem_Click(object sender, EventArgs e) { btnNewCar.PerformClick(); } private void clearSearchToolStripMenuItem_Click(object sender, EventArgs e) { tbxSearch.Text = ""; } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { SafeManager.SaveCars(filepath, cars); Environment.Exit(0); } private void clearRecentFilesToolStripMenuItem_Click(object sender, EventArgs e) { SafeManager.ClearRecentPaths(); } private void settingsToolStripMenuItem_Click(object sender, EventArgs e) { SettingsForm settingsForm = new SettingsForm(); settingsForm.FormClosed += (s2, e2) => { // refresh cars in case data location changed List cars_ = SafeManager.ReadCars(filepath); refreshCars(cars_, false, true); System.Diagnostics.Debug.WriteLine("Refreshed!"); }; settingsForm.ShowDialog(); } } }