Last active
July 6, 2018 11:58
-
-
Save Justasic/e64356ae9c7de8ad2facd1cf383015fc to your computer and use it in GitHub Desktop.
The same as RapidCheckouts.py but written in pure C++ to remove the python dependencies (makes it more portable)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* MIT License | |
* | |
* Copyright (c) 2018 Justin Crawford <[email protected]> | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
// Compile with the following line: | |
// x86_64-w64-mingw32-g++ --std=c++17 -Wall -Wextra -Wno-unused-parameter -mwindows -static -g -o RapidCheckouts.exe RapidCheckouts.cpp -lWinspool -lGdi32 -lUser32 | |
#include <cstdio> | |
#include <cstdlib> | |
#include <iostream> | |
#include <vector> | |
#include <map> | |
#include <string> | |
#include <cstring> | |
#include <strings.h> | |
#include <unistd.h> | |
// #define VC_EXTRALEAN | |
//#define WIN32_LEAN_AND_MEAN | |
#include <Windows.h> | |
#include <Richedit.h> | |
#include <commctrl.h> | |
// Globals | |
HINSTANCE hInst; | |
// Function Prototypes | |
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM); | |
inline COLORREF CreateHexColor(int hex) | |
{ | |
int red = ((hex >> 16) & 0xFF) / 1.0; | |
int green = ((hex >> 8) & 0xFF) / 1.0; | |
int blue = ((hex)& 0xFF) / 1.0; | |
if (red < 256 && green < 256 && blue < 256) | |
return RGB(red, green, blue); | |
return 0; | |
} | |
inline HBRUSH CreateHexBrush(int hex) | |
{ | |
int red = ((hex >> 16) & 0xFF) / 1.0; | |
int green = ((hex >> 8) & 0xFF) / 1.0; | |
int blue = ((hex)& 0xFF) / 1.0; | |
if (red < 256 && green < 256 && blue < 256) | |
return CreateSolidBrush(RGB(red, green, blue)); | |
return NULL; | |
} | |
inline std::string LastError() | |
{ | |
char errbuf[513]; | |
DWORD err = GetLastError(); | |
if (!err) | |
return "No error"; | |
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, errbuf, 512, NULL); | |
return errbuf; | |
} | |
typedef enum | |
{ | |
PORTRATE = 1, | |
LANDSCAPE | |
} Orientations_t; | |
std::map<std::string, std::map<std::string, std::string>> BuildDict() | |
{ | |
PRINTER_INFO_1 *pPrinters = nullptr; | |
DWORD unused = 0; | |
DWORD sizereq = 0; | |
DWORD objects = 0; | |
// First request the size of buffer we need to allocate | |
EnumPrinters(PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, nullptr, 1, reinterpret_cast<LPBYTE>(pPrinters), 0, &sizereq, &unused); | |
// Allocate a 2d array from size required. | |
pPrinters = reinterpret_cast<PRINTER_INFO_1*>(malloc(sizereq)); | |
// Now actually get the list of printers | |
EnumPrinters(PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, nullptr, 1, reinterpret_cast<LPBYTE>(pPrinters), sizereq, &unused, &objects); | |
// Enumerate the printers | |
std::map<std::string, std::map<std::string, std::string>> ret; | |
for (DWORD i = 0; i < objects; ++i) | |
{ | |
std::string name = pPrinters[i].pName; | |
ret[name]["flags"] = std::to_string(pPrinters[i].Flags); | |
ret[name]["description"] = pPrinters[i].pDescription; | |
ret[name]["comment"] = pPrinters[i].pComment; | |
} | |
return ret; | |
} | |
class Document | |
{ | |
HANDLE pHandle; | |
HDC hPrinter; | |
HPEN pen; | |
HFONT font; | |
Orientations_t orient; | |
std::string PrinterName; | |
int scale_factor; | |
public: | |
Document(std::string Printer, Orientations_t o = PORTRATE, size_t sf = 20) : orient(o), PrinterName(Printer), scale_factor(sf) | |
{ | |
} | |
~Document() | |
{ | |
this->end_document(); | |
DeleteObject(this->pen); | |
DeleteObject(this->font); | |
} | |
void begin_document() | |
{ | |
DWORD needed = 0; | |
printf("Using printer \"%s\"\n", this->PrinterName.c_str()); | |
char buffer[2048]; | |
memset(buffer, 0, 2048); | |
strcpy(buffer, this->PrinterName.c_str()); | |
// Open our printer | |
if (!OpenPrinter(buffer, &pHandle, 0)) | |
printf("Error: OpenPrinter failed: %s\n", LastError().c_str()); | |
// Get the DEVMODE pointer | |
GetPrinter(pHandle, 8, nullptr, 0, &needed); | |
PRINTER_INFO_8 *pInfo = reinterpret_cast<PRINTER_INFO_8*>(malloc(needed)); | |
if (!GetPrinter(pHandle, 8, reinterpret_cast<LPBYTE>(pInfo), needed, &needed)) | |
printf("Error: Failed to call GetPrinter: %s\n", LastError().c_str()); | |
DEVMODE *devmode = nullptr; | |
if (pInfo->pDevMode) | |
devmode = pInfo->pDevMode; | |
else | |
printf("Warning: Failed to set paper type and orientation. This likely is due to a driver error or crash and the printer has become unavailable. Please retry this print job again if it does not work\n"); | |
// Set paper size and orientation. | |
devmode->dmPaperSize = DMPAPER_ENV_10; // Envelope #10 | |
devmode->dmOrientation = this->orient; | |
// Get Device Context | |
this->hPrinter = CreateDC("WINSPOOL", this->PrinterName.c_str(), nullptr, devmode); | |
// Set to hundredths of an inch | |
SetMapMode(this->hPrinter, MM_TWIPS); | |
// Start the document. | |
DOCINFO docinfo; | |
memset(&docinfo, 0, sizeof(DOCINFO)); | |
docinfo.lpszDocName = "Rapid Checkouts"; // Print job name | |
docinfo.cbSize = sizeof(DOCINFO); | |
StartDoc(this->hPrinter, &docinfo); | |
// Finally, create a pen to draw with | |
pen = CreatePen(0, (int)this->scale_factor, 0); | |
SelectObject(this->hPrinter, this->pen); | |
free(pInfo); | |
} | |
void end_document() | |
{ | |
EndDoc(this->hPrinter); | |
} | |
void end_page() | |
{ | |
EndPage(this->hPrinter); | |
} | |
void setfont(const std::string &Name, size_t size, bool bold = true) | |
{ | |
this->font = CreateFont(size * this->scale_factor, 0, 0, 0, bold ? 700 : 400, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Arial")); | |
SelectObject(this->hPrinter, this->font); | |
} | |
void text(size_t x, size_t y, const std::string &value) | |
{ | |
TextOut(this->hPrinter, this->scale_factor * x, -1 * this->scale_factor * y, value.c_str(), value.size()); | |
} | |
}; | |
std::vector<std::string> split(std::string str, std::string token) | |
{ | |
std::vector<std::string> result; | |
while (str.size()) | |
{ | |
size_t index = str.find(token); | |
if (index != std::string::npos) | |
{ | |
result.push_back(str.substr(0, index)); | |
str = str.substr(index + token.size()); | |
if (str.size() == 0) | |
result.push_back(str); | |
} | |
else | |
{ | |
result.push_back(str); | |
str = ""; | |
} | |
} | |
return result; | |
} | |
#if 1 | |
int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpSzArgument, int nFunsterStil) | |
{ | |
HWND hwnd; | |
MSG messages; | |
WNDCLASSEX wincl; | |
wincl.hInstance = hInst = hThisInstance; | |
wincl.lpszClassName = "HIE Rapid Checkouts"; | |
wincl.lpfnWndProc = WindowProcedure; | |
wincl.style = CS_DBLCLKS | CS_OWNDC | CS_VREDRAW | CS_HREDRAW; | |
wincl.cbSize = sizeof(WNDCLASSEX); | |
wincl.hIcon = LoadIcon(NULL, IDI_QUESTION); | |
wincl.hIconSm = LoadIcon(NULL, IDI_QUESTION); | |
wincl.hCursor = LoadCursor(NULL, IDC_ARROW); | |
wincl.lpszMenuName = NULL; | |
wincl.cbClsExtra = 0; | |
wincl.cbWndExtra = 0; | |
wincl.hbrBackground = CreateHexBrush(0xF0F0F0); | |
if (!RegisterClassEx(&wincl)) | |
return 0; | |
hwnd = CreateWindowEx( | |
0, // Extended possibilities for variation | |
wincl.lpszClassName, // Class name | |
"Holiday Inn Express: Rapid Checkout Printer", // Window title | |
(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX), | |
CW_USEDEFAULT, // Window decides position | |
CW_USEDEFAULT, // where the window ends up on the screen | |
500, // Width | |
500, // Height | |
HWND_DESKTOP, // The window is a child-window of desktop | |
NULL, // No Menu | |
hThisInstance, | |
NULL // No Creation data | |
); | |
ShowWindow(hwnd, nFunsterStil); | |
while (GetMessage(&messages, NULL, 0, 0)) | |
{ | |
TranslateMessage(&messages); | |
DispatchMessage(&messages); | |
} | |
return messages.wParam; | |
} | |
char Themes[2][30] = | |
{ | |
"HIE Green", | |
"Code Blue" | |
}; | |
// WIndow procedure for handling window drawing and events | |
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lparam) | |
{ | |
static char *strbuf = reinterpret_cast<char *>(malloc((1 << 16))), *sprintfbuf = reinterpret_cast<char *>(malloc((1 << 16))); | |
switch (message) | |
{ | |
case WM_CREATE: | |
{ | |
LOGFONT lf; | |
HFONT hMyFont; | |
auto pPrinters = BuildDict(); | |
DWORD szPrinterList = 42 * pPrinters.size(); | |
memset(&lf, 0, sizeof(LOGFONT)); | |
lf.lfWeight = FW_NORMAL; | |
lf.lfHeight = 15; | |
strcpy(lf.lfFaceName, "Arial"); | |
hMyFont = CreateFontIndirect(&lf); | |
// Draw our buttons (we have 2 for now) | |
CreateWindowEx(WS_EX_CLIENTEDGE, "button", "Print", WS_VISIBLE | WS_CHILD, 350, 450, 65, 20, hwnd, (HMENU)1, NULL, NULL); | |
CreateWindowEx(WS_EX_CLIENTEDGE, "button", "Leave", WS_VISIBLE | WS_CHILD, 420, 450, 65, 20, hwnd, (HMENU)2, NULL, NULL); | |
// Draw our position fields, x axis and y axis | |
CreateWindowEx(WS_EX_CLIENTEDGE, "Edit", "X" /* X axis*/, WS_VISIBLE | WS_CHILD | WS_BORDER, 400, 20, 80, 20, hwnd, (HMENU)3, NULL, NULL); | |
CreateWindowEx(WS_EX_CLIENTEDGE, "Edit", "Y" /* Y axis*/, WS_VISIBLE | WS_CHILD | WS_BORDER, 300, 20, 80, 20, hwnd, (HMENU)4, NULL, NULL); | |
// Apparently this is needed for the rich edit? | |
LoadLibrary("Msftedit.dll"); | |
// Add the rich edit for room numbers | |
CreateWindowExW(WS_EX_CLIENTEDGE, MSFTEDIT_CLASS, L"", ES_MULTILINE | WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP, 10, 45, 470, 390, hwnd, (HMENU)5, NULL, NULL); | |
// Add the combo box, we need to grab it's window object to add items to the list | |
HWND tcmbo = CreateWindowEx(WS_EX_CLIENTEDGE, WC_COMBOBOX, "", CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE, 90, 15, 100, 100, hwnd, (HMENU)6, NULL, NULL); | |
HWND pcmbo = CreateWindowEx(WS_EX_CLIENTEDGE, WC_COMBOBOX, "", CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE, 10, 445, 300, szPrinterList, hwnd, (HMENU)7, NULL, NULL); | |
// Add our labels for stuff | |
CreateWindowEx(0, "STATIC", "Text position from top left", WS_VISIBLE | WS_CHILD, 315, 5, 140, 15, hwnd, (HMENU)8, NULL, NULL); | |
CreateWindowEx(0, "STATIC", "Folder Style: ", WS_VISIBLE | WS_CHILD, 10, 20, 80, 15, hwnd, (HMENU)9, NULL, NULL); | |
CreateWindowEx(0, "STATIC", "X: ", WS_VISIBLE | WS_CHILD, 385, 22, 12, 15, hwnd, (HMENU)10, NULL, NULL); | |
CreateWindowEx(0, "STATIC", "Y: ", WS_VISIBLE | WS_CHILD, 285, 22, 12, 15, hwnd, (HMENU)11, NULL, NULL); | |
// Set char limits to our X,Y coorid boxes of 5 characters | |
SendMessage(GetDlgItem(hwnd, 3), static_cast<UINT>(EM_LIMITTEXT), static_cast<WPARAM>(5), static_cast<LPARAM>(0)); | |
SendMessage(GetDlgItem(hwnd, 4), static_cast<UINT>(EM_LIMITTEXT), static_cast<WPARAM>(5), static_cast<LPARAM>(0)); | |
// Add our list items to the theme combo box | |
for (unsigned i = 0; i < (sizeof(Themes) / 30); ++i) | |
SendMessage(tcmbo, static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(Themes[i])); | |
// Select the default theme (Code Blue for now) | |
SendMessage(tcmbo, CB_SETCURSEL, static_cast<WPARAM>(1), static_cast<LPARAM>(0)); | |
// Send all the text for the printer list to the combo box | |
for (const auto &i : pPrinters) | |
SendMessage(pcmbo, static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(i.first.c_str())); | |
// Again, select the default printer (system default) | |
{ | |
char buffer[2048]; | |
int i = 0; | |
DWORD microsoftism = sizeof(buffer); | |
GetDefaultPrinterA(buffer, µsoftism); | |
for (const auto &it : pPrinters) | |
{ | |
if (it.first == buffer) | |
SendMessage(pcmbo, CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(0)); | |
i++; | |
} | |
} | |
// Create the control fonts since the standard one sucks | |
for (int i = 1; i < 10 + 1; ++i) | |
SendMessage(GetDlgItem(hwnd, i), WM_SETFONT, (WPARAM)hMyFont, 0); | |
break; | |
} | |
case WM_COMMAND: | |
// Print button | |
if (LOWORD(wParam) == 1) | |
{ | |
//HWND output = GetDlgItem(hwnd, 4); | |
//HWND input = GetDlgItem(hwnd, 3); | |
// Get the positions | |
char Xbuffer[5], Ybuffer[5], buffer[2048]; | |
// Get X,Y coorids | |
GetDlgItemTextA(hwnd, 3, Xbuffer, sizeof(Xbuffer)); | |
GetDlgItemTextA(hwnd, 4, Ybuffer, sizeof(Ybuffer)); | |
// Get selected printer | |
GetDlgItemTextA(hwnd, 7, buffer, sizeof(buffer)); | |
// Text -> Integer | |
int x = atoi(Xbuffer), y = atoi(Ybuffer); | |
std::string SelectedPrinter = buffer, text; | |
// Get the values from the RichEdit | |
size_t szRichText = GetWindowTextLength(GetDlgItem(hwnd, 5)); | |
text.resize(szRichText); | |
GetDlgItemTextA(hwnd, 5, &text.front(), text.size()); | |
// Now it gets fun. Create the print job. | |
// Tokenize to a vector of strings for rooms | |
std::vector<std::string> rooms = split(text, "\n"); | |
snprintf(sprintfbuf, 1 << 16, "%zu Envelopes required\n", rooms.size()); | |
// Create our document | |
Document doc(SelectedPrinter); | |
doc.begin_document(); | |
// Set teh font to Arial (what is on the Rapid Checkouts for holiday inn express) | |
doc.setfont("Arial", 32, true); | |
// Iterate the room list, create a page for each receipt, then write the room number on it | |
for (const auto &room : rooms) | |
{ | |
// The exact position of the "Room#" box for the numbers | |
doc.text(x, y, room.c_str()); | |
doc.end_page(); | |
} | |
// Send the document to the print spooler to be dispatched to the selected printer | |
doc.end_document(); | |
MessageBoxA(hwnd, sprintfbuf, "Rapid Checkouts", MB_OK | MB_ICONINFORMATION); | |
} | |
// They changed the combo box option for the Theme | |
if (LOWORD(wParam) == 6) | |
{ | |
char buffer[40]; | |
GetDlgItemTextA(hwnd, 6, buffer, sizeof(buffer)); | |
if (!strcmp(buffer, Themes[0])) | |
{ | |
// Green theme | |
SetDlgItemTextA(hwnd, 3, "195"); | |
SetDlgItemTextA(hwnd, 4, "597"); | |
} | |
if (!strcmp(buffer, Themes[1])) | |
{ | |
// Code blue | |
} | |
} | |
// Quit/Leave/Exit button | |
if (LOWORD(wParam) == 2) | |
PostQuitMessage(0); | |
break; | |
case WM_DESTROY: | |
case WM_CLOSE: | |
free(sprintfbuf); | |
free(strbuf); | |
PostQuitMessage(0); | |
break; | |
default: | |
return DefWindowProc(hwnd, message, wParam, lparam); | |
} | |
return 0; | |
} | |
#else | |
int main(int argc, char **argv) | |
{ | |
// Get the default printer | |
char buffer[2048]; | |
DWORD microsoftism = sizeof(buffer); | |
GetDefaultPrinterA(buffer, µsoftism); | |
auto printers = BuildDict(); | |
int i = 0; | |
int dflt = 0; | |
for (const auto &p : printers) | |
{ | |
printf("[%d] %s -- %s\n", i, p.first.c_str(), p.second.at("description").c_str()); | |
if (p.first == buffer) | |
dflt = i; | |
i++; | |
} | |
printf("Enter printer number ([%d] default): ", dflt); | |
int selection; | |
std::cin >> selection; | |
// Find their selection | |
std::string SelectedPrinter; | |
i = 0; | |
for (const auto &p : printers) | |
{ | |
if (i == selection) | |
SelectedPrinter = p.first; | |
i++; | |
} | |
if (SelectedPrinter.empty()) | |
{ | |
printf("invalid selection, exiting\n"); | |
return EXIT_FAILURE; | |
} | |
printf("%s selected.\n", SelectedPrinter.c_str()); | |
printf("Enter Room List (comma deliminated, use dashes for range): "); | |
std::string values; | |
std::cin >> values; | |
// Tokenize to a vector of strings for rooms | |
std::vector<std::string> rooms = split(values, ","); | |
printf("\n\n%zu Envelopes required\n", rooms.size()); | |
// Create our document | |
Document doc(SelectedPrinter); | |
doc.begin_document(); | |
// Set teh font to Arial (what is on the Rapid Checkouts for holiday inn express) | |
doc.setfont("Arial", 32, true); | |
// Iterate the room list, create a page for each receipt, then write the room number on it | |
for (const auto &room : rooms) | |
{ | |
// The exact position of the "Room#" box for the numbers | |
doc.text(195, 597, room.c_str()); | |
doc.end_page(); | |
} | |
// Send the document to the print spooler to be dispatched to the selected printer | |
doc.end_document(); | |
printf("Press any key to exit.\n"); | |
std::getchar(); | |
return EXIT_SUCCESS; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment