This post is actually what pushed the other two posts out the door. I wanted to write this one, but unless you understood how I got here, it would make no sense. In adding EDT numeric keypad support I’ve run headlong into a Qt design flaw I haven’t seen in years. Mainly, I haven’t seen it because I’ve been doing touchscreen development so the keyboard input stuff hasn’t matter.
QKeyEvent doesn’t have a method that returns a QKeySequence. You can only get an int for the key and the modifiers all monkey piled into another binary field. You can’t construct a QKeySequence from this because order is important when comparing strings. If you just try to thump the modifiers in you end up with this.
edtWord: Ctrl+, Num+, +
keyStr: +, Ctrl+Num+ key: 43 modifiers: 603979776
It thinks you want all of the modifiers pressed together. Adding insult to injury, someone could have actually pressed <Ctrl><NumLock> together.
EDT keypad
Oh, those of you who didn’t start in IT loving EDT, probably need a frame of reference.
The numeric keypad mapping for EDT is basically fixed for all eternity. I’ve written quite a bit of the history in the Help page stub and will write more so you can read it there. The PC world was sold to bookkeepers initially. During the CP/M days Visicalc was possibly the best selling software package and WordStar was right up there with it. Bookkeepers and accountants wanted spreadsheets to run numbers with which meant they wanted a keypad resembling an adding machine.
DEC (Digital Equipment Corporation) was selling midrange (and a few mainframe) computers to business customers. Those customers were writing custom software to run their business. There were droves of people performing data entry for various systems like order processing, inventory management, purchasing, payroll, etc. Those customers wanted a numeric keypad that facilitated efficient numerical entry. You will see it has a comma, period, and minus instead of math functions. PF-1 through PF-4 across the top were Programmable Function keys. When in application mode the application decided what those did. For many VT terminal years, they were the only function keys in existence.
EDT mapped the keypad in the manner you see on the option tab. Had this mapping since some time in the 1970s and nobody will change it now. I need to point out these terminals did not have arrow keys or any of the navigation keys you take for granted today. WORD, EOL, CHAR, LINE, SECT, and PAGE were how we navigated when not searching for a specific thing or line number. A SECTion was 16 lines in whichever direction was current for the editor. ADVANCE or BACKUP. All of the navigation was direction sensitive. Once you hit [4] for ADVANCE or [5] for BACKUP that remained your direction until you changed it.
PC missing key
If you will be so kind as to now take a moment to look at the “standard” PC keyboard numeric keypad you will see there is a key missing.
That big fat plus key the bookkeepers had to have sucked up the space. You will find very few PC editors emulate EDT numeric keypad functionality not because it isn’t still popular (JED and Emacs provide the emulation with a caveat) but because you have to jump through hoops implementing that extra key.
NumLock for GOLD if needed
I coded up all of these things. I don’t have all of the required functions fully implemented, but for the keypad keys, I have everything being caught. For the single letter non-EDT things I like to see hooked to the GOLD key on a PC it all works great. I even got the radio button box in case someone is also using Emacs which cannot map the NumLock key unless you hack the terminal definition. Most people just opt for the ScrollLock key if they aren’t hopping between an actual VMS system and the PC.
When you actually have to handle a key event rather than attach it as a keyboard shortcut, and it is not a simple keystroke, that’s when you get to be Wiley Coyote.
EDT Delete-Word key
QKeyEvent won’t give you a QKeySequence back.
That’s all you can get. To understand how hosed this is you have to look at what comes out if you just try to thump the modifiers and key into a QKeySequence object as-is. I will show you the output again.
edtWord: Ctrl+, Num+, +
keyStr: +, Ctrl+Num+ key: 43 modifiers: 603979776
You can’t put the modifiers in first like you would want to when they are all monkey piled like that because you get an empty string back. A monkey pile as the second parameter works just fine…if the user really did press <Ctrl><NumLock>.
If you look at the options tab again, and if you have played with the current version of Diamond, you will know that the entry field will let them enter most every key combination they want. I always map <Alt> and the [+] from the keypad in any emulator I’m using. Due to Mac support and the existing code to switch <Ctrl> to <Meta> I begrudgingly mapped this as <Ctrl>.
The modifier problem
Putting that aside, the user could enter any combination they want actually. Theoretically they could have mapped it as <Ctrl><NumLock> followed by the [+] from the keypad. Oh, you need another frame of reference.
The Modifier values can’t realistically be used to determine the sequence of keys. To the modifiers structure <Ctrl><Alt> is the same as <Alt><Ctrl> but to a key sequence string, they are different.
I could just hack this. I could force the order and if they didn’t enter that order when changing the value on the tab, joke em!
Contrary to popular belief, I’m not that big of an asshole.
No good quick and dirty working code
I could just hard code these combinations, showing them on the screen, mapping Delete Word to <Alt> and [+] from the numeric keypad; Apple users be damned! When it comes to Apple users, I am that big of an asshole, but not the course of action I am going to immediately pursue. It is my fallback position if my experiments fail.
Now you are caught up to what brought about this rant. I wanted to conduct some experiments without having to rewrite what could be 1000 lines of code just to try something. For the questions I needed answered I didn’t even need GUI. I went searching for just a simple console demo program for CopperSpice and could only find the Kitchen Sink. Hey, I get it. There is a small team working on this and there are only so many hours in the day.
Ordinarily, when you are using Qt you have QtCreator and tell it to create you a shiny new console app and it templates out “Hello World!” Most everyone has gotten in the habit of performing our gene splicing tests inside one of those. We don’t yet have CsCreator in the CopperSpice world. Perhaps, one day, someone will fork Diamond (after I’ve gotten all my stuff in it) to create CsCreator.
Here we go.
I created 3 directories. cs_hello, cs_hello_build and cs_hello_debug because that is currently how I do it using Ninja with Diamond.
You need a CMakeLists.txt and a README.md in the base project directory with an src directory underneath. I thieved and hacked the Diamond CMakeLists.txt I’m using. Mine has compiler options which I’ve been informed is verboten in the project, but, environment variables aren’t reliable or auditable and I come from a medical device background for the past decade.
cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
cmake_policy(VERSION 3.8..3.13)
# support using rpath on Mac OS X
cmake_policy(SET CMP0042 NEW)
project(cs_hello)
set(BUILD_MAJOR "1")
set(BUILD_MINOR "3")
set(BUILD_MICRO "4")
set(BUILD_COMPONENTS "cs_hello")
include(CheckCXXCompilerFlag)
include(CheckCXXSourceCompiles)
include(CheckIncludeFile)
include(CheckIncludeFiles)
include(CheckTypeSize)
find_package(CopperSpice REQUIRED)
# file locations for installing
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
include(GNUInstallDirs)
# indicates where libraries are located relative to the executable
set(CMAKE_INSTALL_RPATH "@executable_path/../Resources")
elseif(CMAKE_SYSTEM_NAME MATCHES "(Linux|OpenBSD|FreeBSD|NetBSD|DragonFly)")
include(GNUInstallDirs)
set(CMAKE_INSTALL_RPATH "$ORIGIN")
# lots of warnings and all warnings as errors
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
elseif(MSVC)
# use defaults
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION .)
include(${CopperSpice_DIR}/InstallMinGW.cmake)
endif()
set(PACKAGE "cs_hello")
set(PACKAGE_NAME "Cs_Hello")
set(PACKAGE_VERSION "${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}")
set(PACKAGE_STRING "cs_hello ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}")
set(PACKAGE_TARNAME "cs_hello")
#set(PACKAGE_BUGREPORT "info@copperspice.com")
#set(PACKAGE_URL "https://www.copperspice.com/")
set(CPACK_PACKAGE_NAME ${PROJECT_NAME} )
set(CPACK_PACKAGE_VENDOR "CopperSpice")
set(CPACK_PACKAGE_VERSION_MAJOR ${BUILD_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${BUILD_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${BUILD_MICRO})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cross platform Hello World sample")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CMAKE_INSTALL_PREFIX})
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
# mac osx only
set(CPACK_BUNDLE_NAME Cs_Hello)
set(CPACK_BUNDLE_ICON "${CMAKE_SOURCE_DIR}/resources/diamond.icns")
set(CPACK_BUNDLE_PLIST "${CMAKE_SOURCE_DIR}/resources/Info.plist")
include(CPack)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
if(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-undefined,error")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-undefined,error")
else()
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
endif()
string(TIMESTAMP BUILD_DATE "%m/%d/%Y")
add_definitions(-DBUILD_DATE="${BUILD_DATE}")
configure_file(
${CMAKE_SOURCE_DIR}/src/cs_hello_build_info.h.in
src/cs_hello_build_info.h
)
# location for building binary files
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(src)
if(${CMAKE_SIZEOF_VOID_P} EQUAL 4)
set(TARGETBITS 32)
else()
set(TARGETBITS 64)
endif()
message("")
message("Cs_Hello configured to run on: ${CMAKE_SYSTEM_NAME} ${TARGETBITS} bit, ${CMAKE_BUILD_TYPE} Mode")
message("Cs_Hello will be built in: ${CMAKE_BINARY_DIR}")
message("Cs_Hello will be installed in: ${CMAKE_INSTALL_PREFIX}")
message("\n")
/**************************************************************************
*
* Copyright (c) 2012-2020 Barbara Geller
*
* Diamond Editor is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* Diamond is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
***************************************************************************/
#ifndef CS_HELLO_BUILD_INFO_H_IN
#define CS_HELLO_BUILD_INFO_H_IN
// Cs_Hello Version "x.y.z"
constexpr const char *versionString = "@PACKAGE_VERSION@";
// Cs_Hello Build Date "08/17/2017"
constexpr const char *buildDate = "@BUILD_DATE@";
#endif
list(APPEND CS_HELLO_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}/cs_hello_build_info.h
)
list(APPEND CS_HELLO_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
# run rcc to generate qrc output
#COPPERSPICE_RESOURCES(
# ${CMAKE_CURRENT_SOURCE_DIR}/../cs_hello.qrc
#)
# run uic to generate source
# .ui files go here
#COPPERSPICE_RESOURCES(
#)
add_executable(Cs_Hello ${CS_HELLO_SOURCES})
target_include_directories(Cs_Hello
PRIVATE
${CMAKE_SOURCE_DIR}/src
)
target_link_libraries(Cs_Hello
PRIVATE
CopperSpice::CsCore
CopperSpice::CsGui
CopperSpice::CsNetwork
)
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
set_target_properties(Cs_Hello PROPERTIES OUTPUT_NAME cs_hello)
elseif(CMAKE_SYSTEM_NAME MATCHES "(Linux|OpenBSD|FreeBSD|NetBSD|DragonFly)")
set_target_properties(Cs_Hello PROPERTIES OUTPUT_NAME cs_hello)
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
install(TARGETS Cs_Hello DESTINATION ../MacOS)
else()
install(TARGETS Cs_Hello DESTINATION .)
endif()
cs_copy_library(CsCore)
cs_copy_library(CsGui)
cs_copy_library(CsNetwork)
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
# copy required plugins to the bundle
cs_copy_plugins(CsGui ../plugins)
#cs_copy_plugins(CsPrinterDriver ../plugins)
else()
# installs required plugins
cs_copy_plugins(CsGui)
#cs_copy_plugins(CsPrinterDriver)
endif()
My console application doesn’t nee GUI or network libraries, but I left them in. I left a lot of stuff in but commented out as well. At some point I will need a cs_gui_hello (just like you) to test some GUI gene splicing.
#include <QCoreApplication>
#include <QTimer>
#include <QDebug>
class Task : public QObject
{
CS_OBJECT(Task)
public:
Task(QObject *parent = 0) : QObject(parent) {}
CS_SLOT_1(Public, void run())
CS_SLOT_2(run)
CS_SIGNAL_1(Public, void finished())
CS_SIGNAL_2(finished)
};
void Task::run()
{
// Do processing here
qDebug() << "Hello World!";
emit finished();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// parent task to application so it will be cleaned up
//
Task *task = new Task(&a);
// make sure we know when things are done
//
QObject::connect(task, SIGNAL(finished()), &a, SLOT(quit()));
// Timer won't run until event loop starts
//
QTimer::singleShot(0, task, SLOT(run()));
// Go for it!
return a.exec();
}
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../cs_hello_debug -DCMAKE_PREFIX_PATH=/usr/lib/cs_lib/lib/cmake/CopperSpice ../cs_hello
Tomorrow I can use this to do some gene splicing for the key sequence testing. I need to sleep on it but I’m thinking about creating a basic class containing the modifier and key values like QKeyEvent is able to provide. I need to see just how well various QKeySequence construction attempts work out also.
One thing is really important. I need to try and get this to binary comparisons. This code will execute with each and every keystroke when EDT mode is enabled. This means I will have to not store QKeySequence::toString() output which will make initial run conversion a bit more involved than it is now.
Oh yeah, remind me to tell you about the file versioning.
I will go through my old posts and make sure I’m not re-hashing that. I honestly did it for performance. On a really slow drive it is “cheaper” to rename the existing file and write a completely new one. Less total I/O.