Compare commits
	
		
			10 Commits
		
	
	
		
			c076fd969e
			...
			1b2ef4240d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1b2ef4240d | |||
| 844b32a6fd | |||
| c1f798be7a | |||
| 8b0efde52a | |||
| 489824b1f9 | |||
| ecca252027 | |||
| ab198d6778 | |||
| d60cedf10d | |||
| fd6a14675b | |||
| bf96e3bc9c | 
| @ -1,5 +1,5 @@ | ||||
| # Mainspring app CMakeLists.txt | ||||
| add_executable(Mainspring WIN32 "src/main.cpp" "src/pch.h" "src/Application.h" "src/Application.cpp" "resource.rc" "src/resources.h") | ||||
| add_executable(Mainspring WIN32 "src/main.cpp" "src/pch.h" "src/Application.h" "src/Application.cpp" "resource.rc" "src/resources.h" "src/utility.h" "src/utility.cpp") | ||||
| 
 | ||||
| set_property(TARGET Mainspring PROPERTY CXX_STANDARD 20) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								Mainspring/res/mainspring.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Mainspring/res/mainspring.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.0 KiB | 
| @ -2,12 +2,61 @@ | ||||
| #include "windows.h" | ||||
| #include "winres.h" | ||||
| #include "src/resources.h" | ||||
| #include "version.h" | ||||
| 
 | ||||
| IDD_DIALOGMAIN DIALOGEX 0, 0, 175, 75 | ||||
| IDI_ICONMAIN ICON "res/mainspring.ico" | ||||
| 
 | ||||
| IDR_MENUMAIN MENU | ||||
| BEGIN | ||||
|     POPUP "&File" | ||||
|     BEGIN | ||||
|         MENUITEM "&Reset\tCtrl+R" ID_FILE_RESET | ||||
|         MENUITEM "&Save Time\tCtrl+S" ID_FILE_SAVE | ||||
|         MENUITEM "Save Time &As...\tCtrl+Shift+S" ID_FILE_SAVEAS | ||||
|         MENUITEM "&Open Time\tCtrl+O" ID_FILE_OPEN | ||||
|         MENUITEM SEPARATOR | ||||
|         MENUITEM "&Exit\tAlt+F4" ID_FILE_EXIT | ||||
|     END | ||||
| 
 | ||||
|     POPUP "&Help" | ||||
|     BEGIN | ||||
|         MENUITEM "&About" ID_HELP_ABOUT | ||||
|     END | ||||
| END | ||||
| 
 | ||||
| IDD_DIALOGMAIN DIALOGEX 0, 0, 200, 70 | ||||
| STYLE WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | ||||
| FONT 8, "MS Shell Dlg", 400, 0, 0x1 | ||||
| MENU IDR_MENUMAIN | ||||
| BEGIN | ||||
|     CTEXT "APPNAME", IDC_STATICTITLE, 30, 5, 115, 20, SS_CENTERIMAGE | ||||
|     CTEXT "TIME", IDC_STATICTIME, 10, 25, 155, 25, SS_CENTERIMAGE | ||||
|     DEFPUSHBUTTON "STARTPAUSE" IDC_BUTTONSTARTPAUSE, 63, 55, 50, 15 | ||||
|     CTEXT "APPNAME", IDC_STATICTITLE, 0, 0, 200, 20, SS_CENTERIMAGE | ||||
|     CTEXT "TIME", IDC_STATICTIME, 8, 20, 184, 28, SS_CENTERIMAGE | SS_SUNKEN | ||||
|     DEFPUSHBUTTON "STARTPAUSE" IDC_BUTTONSTARTPAUSE, 75, 53, 50, 15 | ||||
| END | ||||
| 
 | ||||
| IDA_ACCELMAIN ACCELERATORS | ||||
| BEGIN | ||||
|     "R",    ID_FILE_RESET,    CONTROL, VIRTKEY | ||||
|     "S",    ID_FILE_SAVE,   CONTROL, VIRTKEY | ||||
|     "S",    ID_FILE_SAVEAS, CONTROL, SHIFT, VIRTKEY | ||||
|     "O",    ID_FILE_OPEN,   CONTROL, VIRTKEY | ||||
| END | ||||
| 
 | ||||
| VS_VERSION_INFO VERSIONINFO | ||||
| BEGIN | ||||
|     BLOCK "StringFileInfo" | ||||
|     BEGIN | ||||
|         BLOCK "040904b0" | ||||
|         BEGIN | ||||
|             VALUE "CompanyName", "Grayson Riffe" | ||||
|             VALUE "FileDescription", "Mainspring" | ||||
|             VALUE "ProductName", "Mainspring - A simple timekeeper" | ||||
|             VALUE "ProductVersion", APPVERSION | ||||
|             VALUE "LegalCopyright", "Copyright (C) 2025" | ||||
|         END | ||||
|     END | ||||
|     BLOCK "VarFileInfo" | ||||
|     BEGIN | ||||
|         VALUE "Translation", 0x409, 1200 | ||||
|     END | ||||
| END | ||||
| @ -2,6 +2,14 @@ | ||||
| #include "pch.h" | ||||
| #include "Application.h" | ||||
| #include "resources.h" | ||||
| #include "utility.h" | ||||
| 
 | ||||
| // Define timer IDs
 | ||||
| #define IDT_UPDATEAPP 1 | ||||
| #define IDT_SAVEDTEXT 2 | ||||
| 
 | ||||
| // Define window message to update the timer display
 | ||||
| #define WM_UPDATEAPP WM_USER + 1 | ||||
| 
 | ||||
| // Enable visual styles
 | ||||
| #pragma comment(linker, "\"/manifestdependency:type='win32' \ | ||||
| @ -12,12 +20,31 @@ namespace mainspring { | ||||
|     Application::Application(const char* appName, const char* appVersion) | ||||
|         : m_appName(appName) | ||||
|         , m_appVersion(appVersion) | ||||
|         , m_dlg() | ||||
|         , m_timing(false) | ||||
|         , m_startpauseButtonPressed(false) | ||||
|         , m_elapsedSaved() | ||||
|         , m_startTime() | ||||
|         , m_file() | ||||
|         , m_showSavedText(false) | ||||
|     { | ||||
|         std::cout << std::format("{} {}\n", m_appName, m_appVersion); | ||||
|     } | ||||
| 
 | ||||
|     void Application::run() { | ||||
|         DialogBoxParam(nullptr, MAKEINTRESOURCE(IDD_DIALOGMAIN), nullptr, reinterpret_cast<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(this)); | ||||
|         std::string configPath = getConfigPath(); | ||||
|         std::string recentFile; | ||||
|         if (readFile(configPath.c_str(), recentFile) && !recentFile.empty()) | ||||
|             open(recentFile); | ||||
| 
 | ||||
|         m_dlg = CreateDialogParam(nullptr, MAKEINTRESOURCE(IDD_DIALOGMAIN), nullptr, reinterpret_cast<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(this)); | ||||
| 
 | ||||
|         HACCEL hAccel = LoadAccelerators(GetModuleHandle(NULL), MAKEINTRESOURCE(IDA_ACCELMAIN)); | ||||
|         MSG msg = {}; | ||||
|         ShowWindow(m_dlg, SW_SHOW); | ||||
|         while (GetMessage(&msg, nullptr, 0, 0)) | ||||
|             if (!TranslateAccelerator(m_dlg, hAccel, &msg)) | ||||
|                 IsDialogMessage(m_dlg, &msg); | ||||
|     } | ||||
| 
 | ||||
|     BOOL CALLBACK Application::mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) { | ||||
| @ -28,6 +55,7 @@ namespace mainspring { | ||||
|                 app = reinterpret_cast<Application*>(lParam); | ||||
| 
 | ||||
|                 SetWindowText(dlg, app->m_appName); | ||||
|                 SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)))); | ||||
| 
 | ||||
|                 POINT cursor = {}; | ||||
|                 GetCursorPos(&cursor); | ||||
| @ -36,30 +64,206 @@ namespace mainspring { | ||||
|                 GetMonitorInfo(MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST), &mi); | ||||
|                 SetWindowPos(dlg, nullptr, mi.rcMonitor.left + 100, mi.rcMonitor.top + 100, 0, 0, SWP_NOZORDER | SWP_NOSIZE); | ||||
| 
 | ||||
|                 SetDlgItemText(dlg, IDC_STATICTITLE, app->m_appName); | ||||
|                 LOGFONT lFont = {}; | ||||
|                 lFont.lfHeight = 30; | ||||
|                 HFONT titleFont = CreateFontIndirect(&lFont); | ||||
|                 SendMessage(GetDlgItem(dlg, IDC_STATICTITLE), WM_SETFONT, reinterpret_cast<WPARAM>(titleFont), NULL); | ||||
| 
 | ||||
|                 lFont.lfHeight = 40; | ||||
|                 std::memcpy(lFont.lfFaceName, "Consolas", 9); | ||||
|                 lFont.lfHeight = 60; | ||||
|                 HFONT timeFont = CreateFontIndirect(&lFont); | ||||
|                 SendMessage(GetDlgItem(dlg, IDC_STATICTIME), WM_SETFONT, reinterpret_cast<WPARAM>(timeFont), NULL); | ||||
| 
 | ||||
|                 SetDlgItemText(dlg, IDC_BUTTONSTARTPAUSE, "Start"); | ||||
|                 SendMessage(dlg, WM_UPDATEAPP, NULL, NULL); | ||||
|                 SetTimer(dlg, IDT_UPDATEAPP, 50, nullptr); | ||||
| 
 | ||||
|                 return TRUE; | ||||
|             } | ||||
| 
 | ||||
|             case WM_CLOSE: | ||||
|                 EndDialog(dlg, 0); | ||||
|             case WM_COMMAND: | ||||
|                 switch (LOWORD(wParam)) { | ||||
|                     case IDC_BUTTONSTARTPAUSE: | ||||
|                         app->m_startpauseButtonPressed = true; | ||||
|                         return TRUE; | ||||
| 
 | ||||
|                     case ID_FILE_RESET: { | ||||
|                         if (!app->m_file.empty()) | ||||
|                             app->save(); | ||||
| 
 | ||||
|                         app->reset(); | ||||
|                         return TRUE; | ||||
|                     } | ||||
| 
 | ||||
|                     case ID_FILE_SAVE: { | ||||
|                         app->save(); | ||||
|                         return TRUE; | ||||
|                     } | ||||
| 
 | ||||
|                     case ID_FILE_SAVEAS: { | ||||
|                         app->save(true); | ||||
|                         return TRUE; | ||||
|                     } | ||||
| 
 | ||||
|                     case ID_FILE_OPEN: { | ||||
|                         if (!app->m_file.empty()) | ||||
|                             app->save(); | ||||
| 
 | ||||
|                         app->open(); | ||||
|                         return TRUE; | ||||
|                     } | ||||
| 
 | ||||
|                     case ID_FILE_EXIT: { | ||||
|                         SendMessage(dlg, WM_CLOSE, NULL, NULL); | ||||
|                         return TRUE; | ||||
|                     } | ||||
| 
 | ||||
|                     case ID_HELP_ABOUT: | ||||
|                         MessageBox(dlg, std::format("{} {}\nCopyright Grayson Riffe 2025\ngraysonriffe.com\n\n" | ||||
|                             "\tMainspring is a simple timekeeper. It can be used as a simple stopwatch for short-term activities, or " | ||||
|                             "it can log hours on a longer-term project. Times can be saved to a file, and any subsequent uses will autosave to that file. The most recent " | ||||
|                             "time will be loaded when Mainspring starts.", app->m_appName, app->m_appVersion).c_str(), std::format("About {}", app->m_appName).c_str(), MB_OK); | ||||
|                         return TRUE; | ||||
|                 } | ||||
| 
 | ||||
|                 return FALSE; | ||||
| 
 | ||||
|             case WM_TIMER: | ||||
|                 switch (wParam) { | ||||
|                     case IDT_UPDATEAPP: | ||||
|                         SendMessage(dlg, WM_UPDATEAPP, NULL, NULL); | ||||
|                         return TRUE; | ||||
| 
 | ||||
|                     case IDT_SAVEDTEXT: | ||||
|                         app->m_showSavedText = false; | ||||
|                         return TRUE; | ||||
|                 } | ||||
| 
 | ||||
|                 return FALSE; | ||||
| 
 | ||||
|                 return TRUE; | ||||
| 
 | ||||
|             case WM_UPDATEAPP: { | ||||
|                 if (app->m_startpauseButtonPressed) { | ||||
|                     app->m_startpauseButtonPressed = false; | ||||
| 
 | ||||
|                     if (!app->m_timing) | ||||
|                         app->m_startTime = Clock::now(); | ||||
|                     else | ||||
|                         app->m_elapsedSaved += app->getElapsed(); | ||||
| 
 | ||||
|                     app->m_timing = !app->m_timing; | ||||
|                 } | ||||
| 
 | ||||
|                 if (app->m_showSavedText) | ||||
|                     SetDlgItemText(dlg, IDC_STATICTITLE, "Saved!"); | ||||
| 
 | ||||
|                 else if (!app->m_file.empty()) { | ||||
|                     std::string filename = app->m_file.substr(app->m_file.find_last_of('\\') + 1); | ||||
|                     SetDlgItemText(dlg, IDC_STATICTITLE, filename.c_str()); | ||||
|                 } | ||||
|                 else | ||||
|                     SetDlgItemText(dlg, IDC_STATICTITLE, app->m_appName); | ||||
| 
 | ||||
|                 if (app->m_timing) | ||||
|                     SetDlgItemText(dlg, IDC_BUTTONSTARTPAUSE, "Pause"); | ||||
|                 else | ||||
|                     SetDlgItemText(dlg, IDC_BUTTONSTARTPAUSE, !app->m_elapsedSaved ? "Start" : "Resume"); | ||||
| 
 | ||||
|                 Seconds totalTime = app->m_elapsedSaved + app->getElapsed(); | ||||
| 
 | ||||
|                 uint32_t hr = totalTime / (3600), min = totalTime % (3600) / 60, sec = totalTime % 60; | ||||
|                 SetDlgItemText(dlg, IDC_STATICTIME, std::format("{:03}:{:02}:{:02}", hr, min, sec).c_str()); | ||||
| 
 | ||||
|                 return TRUE; | ||||
|             } | ||||
| 
 | ||||
|             case WM_CLOSE: { | ||||
|                 if (!app->m_file.empty()) | ||||
|                     app->save(); | ||||
| 
 | ||||
|                 std::string configPath = app->getConfigPath(); | ||||
|                 writeFile(configPath.c_str(), app->m_file); | ||||
| 
 | ||||
|                 KillTimer(dlg, 1); | ||||
|                 DestroyWindow(dlg); | ||||
|                 return TRUE; | ||||
|             } | ||||
| 
 | ||||
|             case WM_DESTROY: | ||||
|                 PostQuitMessage(0); | ||||
|                 return TRUE; | ||||
|         } | ||||
| 
 | ||||
|         return FALSE; | ||||
|     } | ||||
| 
 | ||||
|     Application::~Application() { | ||||
|     std::string Application::getConfigPath() { | ||||
|         char appDataPath[MAX_PATH] = {}; | ||||
|         SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, NULL, appDataPath); | ||||
|         return std::string(appDataPath) + "\\" + m_appName + "\\last"; | ||||
|     } | ||||
| 
 | ||||
|     Seconds Application::getElapsed() { | ||||
|         if (m_timing) | ||||
|             return Duration(Clock::now() - m_startTime).count(); | ||||
|         else | ||||
|             return 0; | ||||
|     } | ||||
| 
 | ||||
|     void Application::reset() { | ||||
|         m_timing = false; | ||||
|         m_elapsedSaved = 0; | ||||
|         m_file.clear(); | ||||
|     } | ||||
| 
 | ||||
|     void Application::save(bool saveas) { | ||||
|         if (m_file.empty() || saveas) { | ||||
|             OPENFILENAME ofn = {}; | ||||
|             ofn.lStructSize = sizeof(ofn); | ||||
|             ofn.hwndOwner = m_dlg; | ||||
|             ofn.lpstrFilter = "Mainspring Time File (*.mspring)\0*.mspring\0\0"; | ||||
|             ofn.lpstrDefExt = "mspring"; | ||||
|             char fileBuf[MAX_PATH] = {}; | ||||
|             ofn.lpstrFile = fileBuf; | ||||
|             ofn.nMaxFile = MAX_PATH; | ||||
|             ofn.Flags = OFN_OVERWRITEPROMPT; | ||||
| 
 | ||||
|             if (GetSaveFileName(&ofn)) | ||||
|                 m_file = fileBuf; | ||||
|             else | ||||
|                 return; | ||||
|         } | ||||
| 
 | ||||
|         Seconds totalTime = m_elapsedSaved + getElapsed(); | ||||
|         std::string saveData = std::to_string(totalTime); | ||||
|         writeFile(m_file.c_str(), saveData); | ||||
|         m_showSavedText = true; | ||||
|         SetTimer(m_dlg, IDT_SAVEDTEXT, 3000, nullptr); | ||||
|     } | ||||
| 
 | ||||
|     void Application::open(std::string path) { | ||||
|         if (path.empty()) { | ||||
|             OPENFILENAME ofn = {}; | ||||
|             ofn.lStructSize = sizeof(ofn); | ||||
|             ofn.hwndOwner = m_dlg; | ||||
|             ofn.lpstrFilter = "Mainspring Time File (*.mspring)\0*.mspring\0\0"; | ||||
|             ofn.lpstrDefExt = "mspring"; | ||||
|             char fileBuf[MAX_PATH] = {}; | ||||
|             ofn.lpstrFile = fileBuf; | ||||
|             ofn.nMaxFile = MAX_PATH; | ||||
|             ofn.Flags = OFN_FILEMUSTEXIST; | ||||
| 
 | ||||
|             if (GetOpenFileName(&ofn)) | ||||
|                 m_file = fileBuf; | ||||
|             else | ||||
|                 return; | ||||
|         } | ||||
|         else | ||||
|             m_file = path; | ||||
| 
 | ||||
|         std::string openData; | ||||
|         if (readFile(m_file.c_str(), openData)) | ||||
|             // Update status text;
 | ||||
|             m_elapsedSaved = std::stoull(openData); | ||||
|     } | ||||
| } | ||||
| @ -1,17 +1,39 @@ | ||||
| // Application class header
 | ||||
| 
 | ||||
| namespace mainspring { | ||||
|     using Clock = std::chrono::high_resolution_clock; | ||||
|     using TimePoint = std::chrono::time_point<Clock>; | ||||
|     using Duration = std::chrono::duration<double, std::ratio<1, 1>>; | ||||
| 
 | ||||
|     using Seconds = uint64_t; | ||||
| 
 | ||||
|     class Application { | ||||
|     public: | ||||
|         Application(const char* appName, const char* appVersion); | ||||
| 
 | ||||
|         void run(); | ||||
| 
 | ||||
|         ~Application(); | ||||
|     private: | ||||
|         static BOOL CALLBACK mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam); | ||||
| 
 | ||||
|         std::string getConfigPath(); | ||||
| 
 | ||||
|         Seconds getElapsed(); // Gets the current timing session time
 | ||||
| 
 | ||||
|         void reset(); | ||||
|         void save(bool saveas = false); | ||||
|         void open(std::string path = ""); | ||||
| 
 | ||||
|         const char* m_appName; | ||||
|         const char* m_appVersion; | ||||
| 
 | ||||
|         HWND m_dlg; | ||||
| 
 | ||||
|         // Current state of the app
 | ||||
|         bool m_timing; // Currently timing?
 | ||||
|         bool m_startpauseButtonPressed; // Was the start / pause button pressed?
 | ||||
|         Seconds m_elapsedSaved; // Time saved
 | ||||
|         TimePoint m_startTime; // When the current timing session was started
 | ||||
|         std::string m_file; // Current time file path
 | ||||
|         bool m_showSavedText; // Save confirmation
 | ||||
|     }; | ||||
| } | ||||
| @ -3,6 +3,7 @@ | ||||
| // IO and strings
 | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| #include <filesystem> | ||||
| #include <string> | ||||
| #include <format> | ||||
| 
 | ||||
| @ -11,4 +12,6 @@ | ||||
| 
 | ||||
| // Windows
 | ||||
| #define WIN32_LEAN_AND_MEAN | ||||
| #include <Windows.h> | ||||
| #include <Windows.h> | ||||
| #include <commdlg.h> | ||||
| #include <ShlObj.h> | ||||
| @ -1,6 +1,26 @@ | ||||
| // Win32 resources
 | ||||
| #define IDD_DIALOGMAIN 101 | ||||
| 
 | ||||
| #define IDC_BUTTONSTARTPAUSE 1001 | ||||
| #define IDC_STATICTITLE 1002 | ||||
| #define IDC_STATICTIME 1003 | ||||
| // Icon
 | ||||
| #define IDI_ICONMAIN 101 | ||||
| 
 | ||||
| // Dialog
 | ||||
| #define IDD_DIALOGMAIN  201 | ||||
| 
 | ||||
| // Menu
 | ||||
| #define IDR_MENUMAIN    301 | ||||
| #define ID_FILE_RESET     302 | ||||
| #define ID_FILE_SAVE    303 | ||||
| #define ID_FILE_SAVEAS  304 | ||||
| #define ID_FILE_OPEN    305 | ||||
| #define ID_FILE_EXIT    306 | ||||
| 
 | ||||
| #define ID_HELP_ABOUT   311 | ||||
| 
 | ||||
| // Accelerators
 | ||||
| #define IDA_ACCELMAIN 401 | ||||
| 
 | ||||
| // Controls (text and buttons)
 | ||||
| #define IDC_BUTTONSTARTPAUSE    1001 | ||||
| #define IDC_STATICTITLE         1002 | ||||
| #define IDC_STATICTIME          1003 | ||||
| #define IDC_STATICICON          1004 | ||||
|  | ||||
							
								
								
									
										30
									
								
								Mainspring/src/utility.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Mainspring/src/utility.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // Utility implementation
 | ||||
| #include "pch.h" | ||||
| #include "utility.h" | ||||
| 
 | ||||
| namespace mainspring { | ||||
|     bool readFile(const char* filename, std::string& out) { | ||||
|         std::ifstream fileStream(filename, std::ios::binary | std::ios::ate); | ||||
|         if (!fileStream.is_open()) | ||||
|             return false; | ||||
| 
 | ||||
|         size_t fileSize = fileStream.tellg(); | ||||
|         fileStream.seekg(0); | ||||
|         out.resize(fileSize); | ||||
|         fileStream.read(out.data(), fileSize); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool writeFile(const char* filename, const std::string& in) { | ||||
|         std::string folder = filename; | ||||
|         folder.resize(folder.find_last_of('\\')); | ||||
|         if (!std::filesystem::exists(folder)) | ||||
|             std::filesystem::create_directories(folder); | ||||
|         std::ofstream fileStream(filename, std::ios::binary | std::ios::trunc); | ||||
|         if (!fileStream.is_open()) | ||||
|             return false; | ||||
| 
 | ||||
|         fileStream << in; | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								Mainspring/src/utility.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Mainspring/src/utility.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| // Utility header
 | ||||
| 
 | ||||
| namespace mainspring { | ||||
|     bool readFile(const char* filename, std::string& out); | ||||
|     bool writeFile(const char* filename, const std::string& in); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user