# HG changeset patch # User Teemu Piippo # Date 1656333972 -10800 # Node ID 9d52b119b3f553401004fae08fcefe0ea46b646b # Parent 416e8c49782926899c94a5ce2075ab23317a0396 Sort out versions more, add about page diff -r 416e8c497829 -r 9d52b119b3f5 CMakeLists.txt --- a/CMakeLists.txt Mon Jun 27 02:01:52 2022 +0300 +++ b/CMakeLists.txt Mon Jun 27 15:46:12 2022 +0300 @@ -3,17 +3,16 @@ set(VERSION_MAJOR 1) set(VERSION_MINOR 0) set(VERSION_PATCH 0) +set(COPYRIGHT "Copyright (C) 2013 - 2022 Teemu Piippo") -set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}") -if (NOT ${VERSION_PATCH} EQUAL 0) - set(VERSION_STRING "${VERSION_STRING}.${VERSION_PATCH}") -endif() +set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") string(TOLOWER ${PROJECT_NAME} TARGET_NAME) add_definitions(-DVERSION_MAJOR=${VERSION_MAJOR}) add_definitions(-DVERSION_MINOR=${VERSION_MINOR}) add_definitions(-DVERSION_PATCH=${VERSION_PATCH}) add_definitions(-DAPPNAME="${PROJECT_NAME}") add_definitions(-DVERSION_STRING="${VERSION_STRING}") +add_definitions(-DCOPYRIGHT="${COPYRIGHT}") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") set(OpenGL_GL_PREFERENCE GLVND) @@ -112,6 +111,7 @@ src/widgets/colorselectdialog.h ) set(FORM_FILES + src/about.ui src/mainwindow.ui src/settingseditor/librarieseditor.ui src/settingseditor/settingseditor.ui diff -r 416e8c497829 -r 9d52b119b3f5 src/main.cpp --- a/src/main.cpp Mon Jun 27 02:01:52 2022 +0300 +++ b/src/main.cpp Mon Jun 27 15:46:12 2022 +0300 @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "src/gl/partrenderer.h" #include "src/layers/axeslayer.h" @@ -69,7 +70,7 @@ static void doQtRegistrations() { - QCoreApplication::setApplicationName(::appName); + QCoreApplication::setApplicationName(QStringLiteral(APPNAME)); QCoreApplication::setOrganizationName("hecknology.net"); QCoreApplication::setOrganizationDomain("hecknology.net"); qRegisterMetaType(); @@ -213,16 +214,9 @@ } } -/** - * @brief Updates the title of the main window so to contain the app's name - * and version as well as the open document name. - */ static QString title() { - QString title = ::appName; - title += " "; - title += fullVersionString(); - return title; + return fullVersionString(); } static ColorTable loadColors(const LibrariesModel* libraries) @@ -364,6 +358,23 @@ return result; } +static void about(QWidget* parent) +{ + QDialog dialog{parent}; + Ui_About ui; + ui.setupUi(&dialog); + ui.textBrowser->setHtml( + ui.textBrowser->toHtml() + .replace("%APPNAME%", APPNAME) + .replace("%COPYRIGHT%", COPYRIGHT) + .replace("%VERSION%", detailedVersionString()) + .replace("%REVDATE%", revisionDateString()) + .replace("%QTVERSION%", qVersion()) + ); + dialog.setWindowTitle(QObject::tr("About %1").arg(APPNAME)); + dialog.exec(); +} + int main(int argc, char *argv[]) { doQtRegistrations(); @@ -584,7 +595,7 @@ addRecentlyOpenedFile(*pathPtr); } } - }; + };; const auto actionSaveAs = [&]{ const std::optional modelId = findCurrentModelId(&ui); if (modelId.has_value()) @@ -709,6 +720,11 @@ } } }); + QObject::connect( + ui.actionAbout, + &QAction::triggered, + [&mainWindow]{about(&mainWindow);} + ); mainWindow.tabifyDockWidget(ui.messageLogDock, ui.toolOptionsDock); mainWindow.restoreGeometry(setting()); mainWindow.restoreState(setting()); @@ -720,6 +736,7 @@ restoreSettings(); updateRenderPreferences(&ui, &renderPreferences, &documents); mainWindow.setWindowTitle(title()); + ui.actionAbout->setText(ui.actionAbout->text().arg(APPNAME)); mainWindow.show(); const int result = app.exec(); saveSettings(); diff -r 416e8c497829 -r 9d52b119b3f5 src/mainwindow.ui --- a/src/mainwindow.ui Mon Jun 27 02:01:52 2022 +0300 +++ b/src/mainwindow.ui Mon Jun 27 15:46:12 2022 +0300 @@ -39,7 +39,7 @@ 0 0 729 - 38 + 41 @@ -92,6 +92,7 @@ Help + @@ -474,6 +475,11 @@ Make unofficial + + + About %1 + + diff -r 416e8c497829 -r 9d52b119b3f5 src/version.cpp --- a/src/version.cpp Mon Jun 27 02:01:52 2022 +0300 +++ b/src/version.cpp Mon Jun 27 15:46:12 2022 +0300 @@ -17,36 +17,46 @@ */ #include +#include #include #include #include "src/version.h" -const char* fullVersionString() +#ifdef HG_ALL_TAGS +# define HGTEXT HG_NODE " (" HG_ALL_TAGS ")" +#else +# define HGTEXT HG_NODE +#endif + +QString detailedVersionString() { - if (::BUILD_TYPE != ReleaseBuild) { - return VERSION_STRING "-" HG_DATE_VERSION; - } - else { - return VERSION_STRING; - } + return QStringLiteral(VERSION_STRING "-" HGTEXT) + + " (" + revisionDateString() + ")"; } -static QString makeCommitTimeString() +QString versionString() { - QString result; -#ifdef HG_DATE_TIME - { - char buffer[100]; - constexpr time_t timestamp = HG_DATE_TIME; - strftime (buffer, sizeof buffer, "%d %b %Y", localtime (×tamp)); - result += buffer; - } +#ifndef HG_VERSION_TAG + return detailedVersionString(); +#else + return QStringLiteral(HG_VERSION_TAG); #endif - return result; } -const QString &commitTimeString() +static QString makeFullVersionString() +{ + QString versionstring = APPNAME " " + versionString(); + return versionstring; +} + +const QString& fullVersionString() { - static QString result = makeCommitTimeString(); - return result; + static const QString cached = makeFullVersionString(); + return cached; } + +QString revisionDateString() +{ + const QDateTime dt = QDateTime::fromSecsSinceEpoch(HG_DATE_TIME); + return dt.toString(QObject::tr("d MMMM yyyy")); +} diff -r 416e8c497829 -r 9d52b119b3f5 src/version.h --- a/src/version.h Mon Jun 27 02:01:52 2022 +0300 +++ b/src/version.h Mon Jun 27 15:46:12 2022 +0300 @@ -19,17 +19,6 @@ #pragma once #include -constexpr char appName[] = APPNAME; - -constexpr struct { - int major = VERSION_MAJOR; - int minor = VERSION_MINOR; - int patch = VERSION_PATCH; -} APPVERSION; - -enum BuildType {InternalBuild, ReleaseBuild}; -constexpr BuildType BUILD_TYPE = InternalBuild; - #ifdef DEBUG # undef RELEASE #endif // DEBUG @@ -38,5 +27,7 @@ # undef DEBUG #endif // RELEASE -const char *fullVersionString(); -const QString& commitTimeString(); +const QString& fullVersionString(); +QString detailedVersionString(); +QString versionString(); +QString revisionDateString(); diff -r 416e8c497829 -r 9d52b119b3f5 tools/caseconversions.py --- a/tools/caseconversions.py Mon Jun 27 02:01:52 2022 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -''' -Provides facilities to converting identifier cases. -''' - -# -# Copyright 2015 Teemu Piippo -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER -# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -import string -import re - -def to_one_case (name, tolower): - ''' - Convers name to either all uppercase or all lowercase (depending on the truth value of tolower), using underscores - as delimiters. - ''' - result = "" - targetSet = (string.ascii_lowercase if tolower else string.ascii_uppercase) + string.digits - inverseSet = (string.ascii_uppercase if tolower else string.ascii_lowercase) - isUnderscorable = lambda ch: ch in string.ascii_uppercase \ - or ch in string.whitespace or ch in string.punctuation - previousWasUnderscorable = isUnderscorable (name[0]) - - for ch in name: - if isUnderscorable (ch) and result != "" and not previousWasUnderscorable: - result += "_" - - if ch in inverseSet: - result += (ch.lower() if tolower else ch.upper()) - elif ch in targetSet: - result += ch - previousWasUnderscorable = isUnderscorable (ch) - - return result - -def to_camel_case (name, java = False): - ''' - Converts name to camelcase. If java is False, the first letter will be lowercase, otherwise it will be uppercase. - ''' - result = "" - wantUpperCase = False - - # If it's all uppercase, make it all lowercase so that this algorithm can digest it - if name == name.upper(): - name = name.lower() - - for ch in name: - if ch == '_': - wantUpperCase = True - continue - - if wantUpperCase: - if ch in string.ascii_lowercase: - ch = ch.upper() - wantUpperCase = False - - result += ch - - if java: - match = re.match (r'^([A-Z]+)([A-Z].+)$', result) - if match: - result = match.group (1).lower() + match.group (2) - else: - result = result[0].lower() + result[1:] - else: - result = result[0].upper() + result[1:] - return result - -case_styles = { - 'lower': lambda name: to_one_case (name, tolower=True), - 'upper': lambda name: to_one_case (name, tolower=False), - 'camel': lambda name: to_camel_case (name, java=False), - 'java': lambda name: to_camel_case (name, java=True), -} - -def convert_case (name, style): - ''' - Converts name to the given style. Style may be one of: - - 'lower': 'foo_barBaz' -> 'foo_bar_baz' - - 'upper': 'foo_barBaz' -> 'FOO_BAR_BAZ' - - 'camel': 'foo_barBaz' -> 'FooBarBaz' - - 'java': 'foo_barBaz' -> 'fooBarBaz' - ''' - try: - stylefunc = case_styles[style] - except KeyError: - validstyles = ', '.join (sorted (case_styles.keys())) - raise ValueError ('Unknown style "%s", should be one of: %s' % (style, validstyles)) - else: - return stylefunc (name) \ No newline at end of file diff -r 416e8c497829 -r 9d52b119b3f5 tools/outputfile.py --- a/tools/outputfile.py Mon Jun 27 02:01:52 2022 +0300 +++ b/tools/outputfile.py Mon Jun 27 15:46:12 2022 +0300 @@ -31,31 +31,32 @@ # class OutputFile: - def __init__ (self, filename): + def __init__(self, filename, *, verbose = False): self.filename = filename + self.verbose = verbose + self.body = '' + self.oldsum = '' + def __enter__(self): try: - with open (self.filename, "r") as file: - self.oldsum = file.readline() - self.oldsum = self.oldsum.replace ('// ', '').strip() + with open(self.filename, "r") as file: + self.oldsum = file.readline().strip().removeprefix('// ') except IOError: - self.oldsum = '' - self.body = '' - + pass + return self def write(self, text): self.body += text - - def save(self, verbose = False): + def __exit__(self, *args): from hashlib import sha256 checksum = sha256(self.body.encode('utf-8')).hexdigest() if checksum == self.oldsum: - if verbose: - print ('%s is up to date' % self.filename) + if self.verbose: + print(f'{self.filename} is up to date') pass else: - with open (self.filename, "w") as file: + with open(self.filename, "w") as file: file.write('// %s\n' % checksum) file.write('// This file has been automatically generated. Do not edit by hand\n') file.write('\n') file.write(self.body) - if verbose: - print('%s written' % self.filename) + if self.verbose: + print(f'{self.filename} written ({checksum})') diff -r 416e8c497829 -r 9d52b119b3f5 tools/updaterevision.py --- a/tools/updaterevision.py Mon Jun 27 02:01:52 2022 +0300 +++ b/tools/updaterevision.py Mon Jun 27 15:46:12 2022 +0300 @@ -33,36 +33,47 @@ import argparse import outputfile -def main(): +if __name__ == '__main__': import subprocess from datetime import datetime parser = argparse.ArgumentParser(description='Writes a header file with Hg commit information') parser.add_argument('--cwd', default = '.') parser.add_argument('output') args = parser.parse_args() - f = outputfile.OutputFile(args.output) data = subprocess.check_output(['hg', 'log', '--cwd', args.cwd, '-r.', '--template', - '{node|short} {branch} {date|hgdate}']).decode().replace('\n', '').split(' ') - + '{node|short} {branch} {date|hgdate} {tags}']).decode().replace('\n', '').split() rev = data[0] branch = data[1] timestamp = int(data[2]) + all_tags = set(data[4:]) + try: + version_tag = [tag for tag in all_tags if tag.startswith('v')][0] + except IndexError: + version_tag = None date = datetime.utcfromtimestamp(timestamp) datestring = date.strftime('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000' - if len(rev) > 7: - rev = rev[0:7] - - if subprocess.check_output(['hg', 'id', '--cwd', args.cwd, '-n']).decode().replace('\n', '').endswith('+'): + rev = rev[:7] + modified = subprocess.check_output(['hg', 'id', '--cwd', args.cwd, '-n']).decode().strip().endswith('+') + if modified: rev += '+' - - f.write('#define HG_NODE "%s"\n' % rev) - f.write('#define HG_BRANCH "%s"\n' % branch) - f.write('#define HG_DATE_VERSION "%s"\n' % datestring) - f.write('#define HG_DATE_STRING "%s"\n' % date.strftime('%d %b %Y')) - f.write('#define HG_DATE_TIME %d\n' % int(timestamp)) - if f.save(): - print('%s updated to %s' %(f.filename, rev)) - -if __name__ == '__main__': - main() + # if the source tree is modified, it's not the released version + version_tag = None + if version_tag is not None: + # tags should be the same in released versions whether or not it's tip + # (though released versions shouldn't be tip because hgtags is modified + # to create the tag 🤔, but let's do this anyway) + all_tags -= {'tip'} + with outputfile.OutputFile(args.output, verbose = True) as f: + f.write(f'#define HG_NODE "{rev}"\n') + if branch != 'default': + f.write(f'#define HG_BRANCH "{branch}"\n') + f.write(f'#define HG_DATE_VERSION "{datestring}"\n') + f.write(f'#define HG_DATE_TIME {int(timestamp)}\n') + if all_tags: + f.write(f'#define HG_ALL_TAGS "{" ".join(sorted(all_tags))}"\n') + if version_tag: + f.write(f'#define HG_VERSION_TAG "{version_tag[1:]}"\n') + if 'tip' in all_tags: + f.write('#define HG_TIP\n') + print(f'Current Hg revision: {rev}')