Compare commits
No commits in common. "1b2ef4240d01839d4fc9e4232bf5294baaae0f80" and "c076fd969ef45a47852b3d0c0394e553e46bdc8b" have entirely different histories.
1b2ef4240d
...
c076fd969e
@ -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" "src/utility.h" "src/utility.cpp")
|
||||
add_executable(Mainspring WIN32 "src/main.cpp" "src/pch.h" "src/Application.h" "src/Application.cpp" "resource.rc" "src/resources.h")
|
||||
|
||||
set_property(TARGET Mainspring PROPERTY CXX_STANDARD 20)
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB |
@ -2,61 +2,12 @@
|
||||
#include "windows.h"
|
||||
#include "winres.h"
|
||||
#include "src/resources.h"
|
||||
#include "version.h"
|
||||
|
||||
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
|
||||
IDD_DIALOGMAIN DIALOGEX 0, 0, 175, 75
|
||||
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, 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
|
||||
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
|
||||
END
|
@ -2,14 +2,6 @@
|
||||
#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' \
|
||||
@ -20,31 +12,12 @@ 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() {
|
||||
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);
|
||||
DialogBoxParam(nullptr, MAKEINTRESOURCE(IDD_DIALOGMAIN), nullptr, reinterpret_cast<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(this));
|
||||
}
|
||||
|
||||
BOOL CALLBACK Application::mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
@ -55,7 +28,6 @@ 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);
|
||||
@ -64,206 +36,30 @@ 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);
|
||||
|
||||
std::memcpy(lFont.lfFaceName, "Consolas", 9);
|
||||
lFont.lfHeight = 60;
|
||||
lFont.lfHeight = 40;
|
||||
HFONT timeFont = CreateFontIndirect(&lFont);
|
||||
SendMessage(GetDlgItem(dlg, IDC_STATICTIME), WM_SETFONT, reinterpret_cast<WPARAM>(timeFont), NULL);
|
||||
|
||||
SendMessage(dlg, WM_UPDATEAPP, NULL, NULL);
|
||||
SetTimer(dlg, IDT_UPDATEAPP, 50, nullptr);
|
||||
SetDlgItemText(dlg, IDC_BUTTONSTARTPAUSE, "Start");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
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);
|
||||
case WM_CLOSE:
|
||||
EndDialog(dlg, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::string Application::getConfigPath() {
|
||||
char appDataPath[MAX_PATH] = {};
|
||||
SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, NULL, appDataPath);
|
||||
return std::string(appDataPath) + "\\" + m_appName + "\\last";
|
||||
}
|
||||
Application::~Application() {
|
||||
|
||||
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,39 +1,17 @@
|
||||
// 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,7 +3,6 @@
|
||||
// IO and strings
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <format>
|
||||
|
||||
@ -13,5 +12,3 @@
|
||||
// Windows
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <commdlg.h>
|
||||
#include <ShlObj.h>
|
@ -1,26 +1,6 @@
|
||||
// Win32 resources
|
||||
#define IDD_DIALOGMAIN 101
|
||||
|
||||
// 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
|
||||
|
@ -1,30 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
// 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