chore(cpn): Fetch maxlibqt from forked repo at compile time (#4249)

Co-authored-by: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com>
This commit is contained in:
Neil Horne
2023-10-27 18:31:17 +10:00
committed by GitHub
co-authored by raphaelcoeffic
parent 34bee45f61
commit a891c5e907
19 changed files with 33 additions and 1074 deletions
+17
View File
@@ -0,0 +1,17 @@
# Fetch maxLibQt source code from Github
include(FetchContent)
FetchContent_Declare(
maxLibQt
GIT_REPOSITORY https://github.com/edgetx/maxLibQt
GIT_TAG b5418f76cc4891e09f4e21276175d39dbb130f66
)
FetchContent_MakeAvailable(maxLibQt)
message("Fetched maxLibQt source code from Github: ${maxLibQt_SOURCE_DIR}")
include_directories(
${maxLibQt_SOURCE_DIR}
${maxLibQt_BINARY_DIR}
)
+5 -4
View File
@@ -142,7 +142,6 @@ endif()
############# Common lib ###############
set(common_SRCS
appdebugmessagehandler.cpp
customdebug.cpp
helpers.cpp
translations.cpp
@@ -151,7 +150,6 @@ set(common_SRCS
)
set(common_MOC_HDRS
appdebugmessagehandler.h
helpers.h
modeledit/node.h
)
@@ -170,7 +168,7 @@ target_link_libraries(common
firmwares
storage
simulation
maxLibQtWidgets
maxLibQt
Qt5::Core
Qt5::Xml
Qt5::Widgets
@@ -185,6 +183,8 @@ set(CPN_COMMON_LIB common)
include(FetchMiniz)
include(FetchMaxLibQt)
add_subdirectory(datamodels)
add_subdirectory(firmwares)
add_subdirectory(shared)
@@ -193,7 +193,6 @@ add_subdirectory(generaledit)
add_subdirectory(simulation)
add_subdirectory(storage)
add_subdirectory(thirdparty/qcustomplot)
add_subdirectory(thirdparty/maxlibqt/src/widgets)
add_subdirectory(updates)
set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Enable testing" FORCE)
@@ -204,6 +203,8 @@ add_subdirectory(thirdparty/yaml-cpp)
############# Companion ###############
include_directories(${maxLibQt_SOURCE_DIR}/widgets)
set(companion_SRCS
helpers_html.cpp
mdichild.cpp
-163
View File
@@ -1,163 +0,0 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program 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.
*
* This program 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. See the
* GNU General Public License for more details.
*/
#include "appdebugmessagehandler.h"
#include <QFileDevice>
AppDebugMessageHandler::AppDebugMessageHandler(QObject * parent) :
QObject(parent),
m_appDebugOutputLevel(APP_DBG_HANDLER_DEFAULT_LEVEL),
m_showSourcePath(APP_DBG_HANDLER_SHOW_SRC_PATH),
m_showFunctionDeclarations(APP_DBG_HANDLER_SHOW_FUNCTION_DECL)
{
m_srcPathFilter = QRegularExpression("^" APP_DBG_HANDLER_SRC_PATH "[\\\\\\/](.*?)$", QRegularExpression::InvertedGreedinessOption);
m_functionFilter = QRegularExpression("^.+?(\\w+\\().+$");
}
AppDebugMessageHandler * AppDebugMessageHandler::instance()
{
static AppDebugMessageHandler * _instance = NULL;
#if APP_DBG_HANDLER_ENABLE
if(_instance == NULL && !qApp->closingDown())
_instance = new AppDebugMessageHandler(qApp); // Ensure this object is cleaned up when the QApplication exits.
#endif
return _instance;
}
void AppDebugMessageHandler::setAppDebugOutputLevel(const quint8 & appDebugOutputLevel)
{
m_appDebugOutputLevel = appDebugOutputLevel;
}
void AppDebugMessageHandler::setShowSourcePath(bool showSourcePath)
{
m_showSourcePath = showSourcePath;
}
void AppDebugMessageHandler::setShowFunctionDeclarations(bool showFunctionDeclarations)
{
m_showFunctionDeclarations = showFunctionDeclarations;
}
void AppDebugMessageHandler::addOutputDevice(QIODevice * device)
{
if (device && !m_outputDevices.contains(device))
m_outputDevices.append(device);
}
void AppDebugMessageHandler::removeOutputDevice(QIODevice * device)
{
if (device) {
// no QVector::removeAll() in Qt < 5.4
int i = 0;
foreach (QIODevice * d, m_outputDevices) {
if (d == device)
m_outputDevices.remove(i);
++i;
}
}
}
void AppDebugMessageHandler::installAppMessageHandler()
{
m_defaultHandler = qInstallMessageHandler(g_appDebugMessageHandler);
}
void AppDebugMessageHandler::messageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg)
{
static const char symbols[] = { 'D', 'W', 'E', 'X', 'I' }; // correspond to QtMsgType enum
// normalize types, QtDebugMsg stays 0, QtInfoMsg becomes 1, rest are QtMsgType + 1
quint8 lvl = type;
if (type == QtInfoMsg)
lvl = 1;
else if (type > QtDebugMsg)
++lvl;
if (lvl < m_appDebugOutputLevel && type != QtFatalMsg)
return;
#if defined(Q_OS_LINUX) && (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
// Filter out lots of QPainter warnings from undocked QDockWidgets... hackish but effective (only workaround found so far)
if (lvl == 2 && QString(context.function).contains("QPainter::"))
return;
#endif
QString msgPattern = QString("[%1] ").arg(symbols[type]);
if (m_showSourcePath) {
QString file = QString("%1::").arg(context.file);
file.replace(m_srcPathFilter, "\\1");
msgPattern.append(file);
}
if (m_showFunctionDeclarations)
msgPattern.append(context.function);
else
msgPattern.append("%{function}()");
msgPattern.append(":%{line} -%{if-category} [%{category}]%{endif} %{message}");
if (type == QtFatalMsg)
msgPattern.append("\nBACKTRACE:\n%{backtrace depth=12 separator=\"\n\"}");
qSetMessagePattern(msgPattern);
if (!m_defaultHandler || m_outputDevices.size() || receivers(SIGNAL(messageOutput(quint8, const QString &)))) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
msgPattern = qFormatLogMessage(type, context, msg);
#else
msgPattern.replace("%{line}", QString::number(context.line));
msgPattern.replace("%{if-category} [%{category}]%{endif}", QString(context.category));
msgPattern.replace("%{message}", msg);
if (!m_showFunctionDeclarations) {
QString func = context.function;
msgPattern.replace("%{function}", func.replace(m_functionFilter, "\\1)"));
}
#endif
emit messageOutput(lvl, msgPattern);
foreach (QIODevice * d, m_outputDevices) {
if (d && d->isWritable() && (!d->property("level").isValid() || d->property("level").toInt() <= lvl)) {
d->write(qPrintable(msgPattern % "\n"));
if (QFileDevice * fd = qobject_cast<QFileDevice *>(d))
fd->flush();
}
}
}
// if (QThread::currentThread() == qApp->thread()) // gui thread
if (m_defaultHandler) {
m_defaultHandler(type, context, msg);
}
else {
fprintf(stderr, "%s", qPrintable(msgPattern));
if (type == QtFatalMsg)
abort();
}
}
// Message handler which is installed using qInstallMessageHandler. This needs to be global.
void g_appDebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (AppDebugMessageHandler::instance())
AppDebugMessageHandler::instance()->messageHandler(type, context, msg);
}
-142
View File
@@ -1,142 +0,0 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program 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.
*
* This program 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. See the
* GNU General Public License for more details.
*/
#ifndef APPDEBUGMESSAGEHANDLER_H
#define APPDEBUGMESSAGEHANDLER_H
#include <cstdlib>
#include <iostream>
#include <QApplication>
#include <QDebug>
#include <QIODevice>
#include <QMessageLogContext>
#include <QObject>
#include <QRegularExpression>
#include <QString>
/*
* Custom debug/message handler class to work in conjunction with qDebug() family of functions.
* Formats all stderr output generated by the application in a consistent manner.
* It can also be used by other event listeners to intercept messages they may be interested in (connect to messageOutput() signal).
*
* Include this header to use qInfo() safely with Qt < v5.5 (qInfo becomes alias for qDebug).
*
* Note that the default message format can still be overriden by setting QT_MESSAGE_PATTERN environment variable at _runtime_.
* see: https://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern
*
* This is an app-wide "global" thread-safe singleton class, use it with AppDebugMessageHandler::instance().
* For example, at start of application:
*
* QApplication app(argc, argv);
* AppDebugMessageHandler::instance()->installAppMessageHandler();
*
* To connect to a signal:
*
* connect(AppDebugMessageHandler::instance(), &AppDebugMessageHandler::messageOutput, this, &DebugOutput::onAppDebugMessage);
*
* This singleton will self-destruct upon QApplication termination.
*
*/
#if defined(QT_DEBUG) || defined(DEBUG)
#define APP_DBG_HANDLER_IS_DBG_BUILD 1
#else
#define APP_DBG_HANDLER_IS_DBG_BUILD 0
#endif
// Enable/disable this custom handler handler entirely
#ifndef APP_DBG_HANDLER_ENABLE
#define APP_DBG_HANDLER_ENABLE 1
#endif
// Default log level (can also be set with setAppDebugOutputLevel()):
// 0 = debug+; 1 = info+; 2 = warn+; 3 = critical+; 4 = fatal only
#ifndef APP_DBG_HANDLER_DEFAULT_LEVEL
#define APP_DBG_HANDLER_DEFAULT_LEVEL 0
#endif
// Include source file path/name in output (filtered with APP_DBG_HANDLER_SRC_PATH)
#ifndef APP_DBG_HANDLER_SHOW_SRC_PATH
#define APP_DBG_HANDLER_SHOW_SRC_PATH APP_DBG_HANDLER_IS_DBG_BUILD
#endif
// Show just the function Class::name vs. full declaration with return and param types
#ifndef APP_DBG_HANDLER_SHOW_FUNCTION_DECL
#define APP_DBG_HANDLER_SHOW_FUNCTION_DECL 0
#endif
// base path for source file name filter, everything after this is kept; RegEx, non-greedy
#ifndef APP_DBG_HANDLER_SRC_PATH
#define APP_DBG_HANDLER_SRC_PATH ".*src"
#endif
// Make sure to include this header if using qInfo()
#if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0))
#if defined(QT_NO_INFO_OUTPUT)
#define qInfo QT_NO_QDEBUG_MACRO
#else
#define qInfo qDebug
#endif
#define QtInfoMsg QtMsgType(4)
#endif
class AppDebugMessageHandler : public QObject
{
Q_OBJECT
public:
// This class implements the singleton design pattern and is therefore publicly accessible only through instance().
static AppDebugMessageHandler * instance();
AppDebugMessageHandler(AppDebugMessageHandler const &) = delete;
void operator = (AppDebugMessageHandler const &) = delete;
void setAppDebugOutputLevel(const quint8 & appDebugOutputLevel);
void setShowSourcePath(bool showSourcePath);
void setShowFunctionDeclarations(bool showFunctionDeclarations);
void addOutputDevice(QIODevice * device);
void removeOutputDevice(QIODevice * device);
void installAppMessageHandler();
void messageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg);
private:
// This class implements the singleton design pattern and therefore has only a private constructor.
explicit AppDebugMessageHandler(QObject * parent = Q_NULLPTR);
~AppDebugMessageHandler() { }
QtMessageHandler m_defaultHandler;
QRegularExpression m_srcPathFilter;
QRegularExpression m_functionFilter;
quint8 m_appDebugOutputLevel;
QVector<QIODevice *> m_outputDevices;
bool m_showSourcePath;
bool m_showFunctionDeclarations;
signals:
void messageOutput(quint8 level, const QString & msg);
};
// Message handler which is installed using qInstallMessageHandler. This needs to be global.
// Use AppDebugMessageHandler::instance()->installAppMessageHandler();
// instead of installing this function directly (it will verify this handler is enabled, etc).
void g_appDebugMessageHandler(QtMsgType, const QMessageLogContext &, const QString &);
#endif // APPDEBUGMESSAGEHANDLER_H
+2 -1
View File
@@ -29,7 +29,8 @@
#undef main
#endif
#include "appdebugmessagehandler.h"
#include <AppDebugMessageHandler>
#include "customdebug.h"
#include "mainwindow.h"
#include "version.h"
+1 -1
View File
@@ -1,4 +1,4 @@
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${maxLibQt_SOURCE_DIR}/widgets)
set(generaledit_NAMES
generaledit
+1
View File
@@ -2,6 +2,7 @@ include_directories(
${CMAKE_SOURCE_DIR}
${COMPANION_SRC_DIRECTORY}
${CMAKE_CURRENT_BINARY_DIR}
${maxLibQt_SOURCE_DIR}/widgets
)
set(modeledit_NAMES
+1 -1
View File
@@ -1,4 +1,4 @@
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${maxLibQt_SOURCE_DIR}/widgets)
set(shared_NAMES
genericpanel
+1 -1
View File
@@ -21,10 +21,10 @@
#include "debugoutput.h"
#include "ui_debugoutput.h"
#include "appdebugmessagehandler.h"
#include "appdata.h"
#include "filteredtextbuffer.h"
#include <AppDebugMessageHandler>
#include <QElapsedTimer>
#include <QMessageBox>
#include <QRegularExpression>
+2 -1
View File
@@ -21,7 +21,7 @@
#include "simulatorwidget.h"
#include "ui_simulatorwidget.h"
#include "appdebugmessagehandler.h"
#include "appdata.h"
#include "radiofaderwidget.h"
#include "radiokeywidget.h"
#include "radioknobwidget.h"
@@ -37,6 +37,7 @@
#include "joystickdialog.h"
#endif
#include <AppDebugMessageHandler>
#include <QFile>
#include <QMessageBox>
#include <iostream>
+1 -1
View File
@@ -29,9 +29,9 @@
#include <QMutex>
#include <QMutexLocker>
#include <QMouseEvent>
#include <AppDebugMessageHandler>
#include "appdata.h"
#include "appdebugmessagehandler.h"
#define LCD_WIDGET_REFRESH_PERIOD 16 // [ms] 16 = 62.5fps
+2 -1
View File
@@ -29,8 +29,9 @@
#undef main
#endif
#include <AppDebugMessageHandler>
#include "appdata.h"
#include "appdebugmessagehandler.h"
#include "constants.h"
#include "customdebug.h"
#include "eeprominterface.h"
@@ -1,31 +0,0 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_policy(SET CMP0020 NEW)
find_package(Qt5Core)
find_package(Qt5Widgets)
project(maxLibQtWidgets)
set(SRCS
"${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.cpp"
# "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.cpp"
"${CMAKE_CURRENT_LIST_DIR}/TimerEdit.cpp"
# "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.cpp"
)
set(HDRS
"${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.h"
# "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.h"
"${CMAKE_CURRENT_LIST_DIR}/TimerEdit.h"
# "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.h"
)
qt5_wrap_cpp(SRCS ${HDRS})
add_library(${PROJECT_NAME} ${SRCS} ${HDRS})
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Widgets)
target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER)
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC "${CMAKE_CURRENT_LIST_DIR}")
# need to push this upstream
set(BUILT_LIBRARIES ${BUILT_LIBRARIES} ${PROJECT_NAME} PARENT_SCOPE)
@@ -1 +0,0 @@
#include "ExportableTableView.h"
@@ -1,242 +0,0 @@
/*
ExportableTableView
COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
Contact: http://www.WorldDesign.com/contact
LICENSE:
Commercial License Usage
Licensees holding valid commercial licenses may use this file in
accordance with the terms contained in a written agreement between
you and the copyright holder.
GNU General Public License Usage
Alternatively, this file may be used under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program 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. See the
GNU General Public License for more details.
A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
*/
#include "ExportableTableView.h"
ExportableTableView::ExportableTableView(QWidget *parent) : QTableView(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
setHtmlTemplate(getDefaultHtmlTemplate());
QAction * selAll = new QAction(tr("Select All"), this);
selAll->setShortcut(QKeySequence::SelectAll);
addAction(selAll);
QAction * cpyTab = new QAction(tr("Copy selection as TAB-delimited text"), this);
cpyTab->setShortcut(QKeySequence::Copy);
cpyTab->setProperty("delim", "\t");
addAction(cpyTab);
QAction * cpyCsv = new QAction(tr("Copy selection as cooma-delimited text (CSV)"), this);
cpyCsv->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
cpyCsv->setProperty("delim", ", ");
addAction(cpyCsv);
QAction * cpyPipe = new QAction(tr("Copy selection as pipe-delimited text"), this);
cpyPipe->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_C);
cpyPipe->setProperty("delim", " | ");
addAction(cpyPipe);
QAction * cpyHtml = new QAction(tr("Copy selection as HTML"), this);
cpyHtml->setProperty("delim", "html");
addAction(cpyHtml);
QAction * saveFile = new QAction(tr("Save selection to file"), this);
saveFile->setShortcut(QKeySequence::Save);
addAction(saveFile);
connect(selAll, &QAction::triggered, this, &QTableView::selectAll);
connect(cpyTab, &QAction::triggered, this, &ExportableTableView::copy);
connect(cpyCsv, &QAction::triggered, this, &ExportableTableView::copy);
connect(cpyPipe, &QAction::triggered, this, &ExportableTableView::copy);
connect(cpyHtml, &QAction::triggered, this, &ExportableTableView::copy);
connect(saveFile, &QAction::triggered, this, &ExportableTableView::save);
connect(this, &QTableView::customContextMenuRequested, this, &ExportableTableView::onCustomContextMenuRequested);
}
QString ExportableTableView::getDefaultHtmlTemplate()
{
return "<!DOCTYPE html>\n<html>\n<head><meta charset='utf-8'/><style>" \
"th, td {font-family: sans-serif; padding: 3px 15px 3px 3px;}" \
"</style></head>\n<body>\n" \
"<table border=0 cellspacing=2>\n" \
"%1" \
"</table>\n" \
"</body></html>";
}
void ExportableTableView::setHtmlTemplate(const QString &value)
{
m_htmlTemplate = value;
}
QString ExportableTableView::toPlainText(const QModelIndexList &indexList, const QString &delim) const
{
QString ret, header;
const QChar rowDelim = '\n';
bool firstRow = true;
for (int i = 0; i < indexList.count(); ++i) {
const QModelIndex & idx = indexList.at(i);
if (firstRow)
header.append(model()->headerData(idx.column(), Qt::Horizontal).toString());
ret.append(idx.data(Qt::DisplayRole).toString());
if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) {
ret.append(rowDelim);
if (firstRow && !header.isEmpty())
header.append(rowDelim);
firstRow = false;
}
else {
ret.append(delim);
if (firstRow && !header.isEmpty())
header.append(delim);
}
}
if (!header.isEmpty())
ret.prepend(header);
return ret;
}
QString ExportableTableView::toHtml(const QModelIndexList &indexList) const
{
QString ret, header, row;
bool firstRow = true;
for (int i = 0; i < indexList.count(); ++i) {
const QModelIndex & idx = indexList.at(i);
const int algn = (idx.data(Qt::TextAlignmentRole).isValid() ? idx.data(Qt::TextAlignmentRole).toInt() : (Qt::AlignLeft | Qt::AlignVCenter));
const QString fg = (idx.data(Qt::ForegroundRole).isValid() ? idx.data(Qt::ForegroundRole).value<QBrush>().color().name(QColor::HexRgb) : "initial");
const QString bg = (idx.data(Qt::BackgroundRole).isValid() ? idx.data(Qt::BackgroundRole).value<QBrush>().color().name(QColor::HexRgb) : "initial");
const QString ttl = (idx.data(Qt::ToolTipRole).isValid() ? idx.data(Qt::ToolTipRole).toString().replace("\"", "\"\"") : QString());
QString fnt;
if (idx.data(Qt::FontRole).isValid()) {
const QFont font = idx.data(Qt::FontRole).value<QFont>();
fnt = "font: \"" % font.family() % "\" " % QString::number(font.pointSize()) % "pt;";
}
if (firstRow)
header.append(QString("<th align='left'>%1</th>\n").arg(model()->headerData(idx.column(), Qt::Horizontal).toString()));
row.append("<td style='color: %2; background-color: %3; %4' align='%5' valign='%6' %7>%1</td>\n"); // cell template
row = row.arg(idx.data(Qt::DisplayRole).toString()).arg(fg).arg(bg).arg(fnt);
row = row.arg((algn & Qt::AlignRight) ? "right" : (algn & Qt::AlignHCenter) ? "center" : "left");
row = row.arg((algn & Qt::AlignTop) ? "top" : (algn & Qt::AlignBottom) ? "bottom" : "middle");
row = row.arg(ttl.isEmpty() ? ttl : QString("title=\"%1\"").arg(ttl));
if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) {
ret.append(QString("<tr>\n%1</tr>\n").arg(row));
row.clear();
firstRow = false;
}
}
ret = QString("<tbody>\n%1</tbody>\n").arg(ret);
if (!header.isEmpty())
ret.prepend(QString("<thead>\n<tr>\n%1</tr>\n</thead>\n").arg(header));
ret = m_htmlTemplate.arg(ret);
return ret;
}
bool ExportableTableView::saveToFile(const QModelIndexList &indexList, const QString &fileName)
{
static QString lastDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
if (indexList.isEmpty())
return false;
QString fname = fileName;
if (fname.isEmpty()) {
QString types = tr("Tab-delimited text") % " (*.tab);;" % tr("Comma-delimited text") % " (*.csv);;" % tr("Pipe-delimited text") % " (*.txt);;" % tr("HTML") % " (*.html)";
fname = QFileDialog::getSaveFileName(this, tr("Save to file"), lastDir, types);
}
if (fname.isEmpty())
return false;
QFile file(fname);
QFileInfo fi(file);
lastDir = fi.absolutePath();
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
if (fi.suffix() == "html")
file.write(toHtml(indexList).toUtf8());
else
file.write(toPlainText(indexList, (fi.suffix() == "tab" ? "\t" : fi.suffix() == "csv" ? ", " : " | ")).toUtf8());
file.close();
return true;
}
QSize ExportableTableView::sizeHint() const
{
int w = 0;
for (int i = 0; i < model()->columnCount(); ++i)
w += sizeHintForColumn(i);
return QSize(w + verticalScrollBar()->sizeHint().width() + 60, sizeHintForRow(0) * model()->rowCount());
}
void ExportableTableView::copyText(const QString &delim)
{
QApplication::clipboard()->setText(toPlainText(getSelectedOrAll(), delim));
}
void ExportableTableView::copyHtml()
{
QMimeData * mdata = new QMimeData;
mdata->setHtml(toHtml(getSelectedOrAll()));
QApplication::clipboard()->setMimeData(mdata);
}
void ExportableTableView::copy()
{
QString delim = "\t";
if (sender() && sender()->property("delim").isValid())
delim = sender()->property("delim").toString();
if (delim == "html")
copyHtml();
else
copyText(delim);
}
void ExportableTableView::save()
{
saveToFile(getSelectedOrAll());
}
QModelIndexList ExportableTableView::getSelectedOrAll()
{
QModelIndexList sel = selectionModel()->selectedIndexes();
if (sel.isEmpty()) {
selectAll();
sel = selectionModel()->selectedIndexes();
}
return sel;
}
void ExportableTableView::onCustomContextMenuRequested(const QPoint & pos)
{
QMenu menu;
menu.addActions(actions());
menu.exec(mapToGlobal(pos));
}
@@ -1,99 +0,0 @@
/*
ExportableTableView
https://github.com/mpaperno/maxLibQt
COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
Contact: http://www.WorldDesign.com/contact
LICENSE:
Commercial License Usage
Licensees holding valid commercial licenses may use this file in
accordance with the terms contained in a written agreement between
you and the copyright holder.
GNU General Public License Usage
Alternatively, this file may be used under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program 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. See the
GNU General Public License for more details.
A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
*/
#ifndef EXPORTABLETABLEVIEW_H
#define EXPORTABLETABLEVIEW_H
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QFile>
#include <QFileDialog>
#include <QMenu>
#include <QMimeData>
#include <QScrollBar>
#include <QStandardPaths>
#include <QTableView>
/**
\class ExportableTableView
\version 1.0.0
\brief The ExportableTableView class provides a regular QTableView but with features to export the data
as plain text or HTML.
Any selection of data can be exported. The horizontal headings, if any, are included in the export.
The export functions are available to the user via a custom context menu or keyboard shortcuts.
3 field delimiter choices are available for plain-text export (tab, comma, or pipe).
Data can be saved to the clipboard or to a file.
The export functions can also be accessed programmatically via \c toPlainText(), \c toHtml(),
and \c saveToFile().
The overall HTML template can be customized by setting \c setHtmlTemplate(). The data itself is always
formatted as a basic HTML table and then inserted into the template at the \c %1 placeholder.
HTML version tries to preserve many data role attributes of the model items:
\li \c Qt::FontRole
\li \c Qt::ForegroundRole
\li \c Qt::BackgroundRole
\li \c Qt::TextAlignmentRole
\li \c Qt::ToolTipRole
Note that \c Qt::EditRole data is not specifically preserved (unless it already matches \c Qt::DisplayRole ).
*/
class ExportableTableView : public QTableView
{
Q_OBJECT
public:
ExportableTableView(QWidget *parent = Q_NULLPTR);
static QString getDefaultHtmlTemplate();
void setHtmlTemplate(const QString &value);
QString toPlainText(const QModelIndexList &indexList, const QString &delim = "\t") const;
QString toHtml(const QModelIndexList &indexList) const;
bool saveToFile(const QModelIndexList &indexList, const QString &fileName = QString());
QSize sizeHint() const Q_DECL_OVERRIDE;
public slots:
void copyText(const QString &delim = QString("\t"));
void copyHtml();
void copy();
void save();
QModelIndexList getSelectedOrAll();
protected:
void onCustomContextMenuRequested(const QPoint &pos);
QString m_htmlTemplate;
};
#endif // EXPORTABLETABLEVIEW_H
@@ -1 +0,0 @@
#include "TimerEdit.h"
@@ -1,281 +0,0 @@
/*
TimerEdit
COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
Contact: http://www.WorldDesign.com/contact
LICENSE:
Commercial License Usage
Licensees holding valid commercial licenses may use this file in
accordance with the terms contained in a written agreement between
you and the copyright holder.
GNU General Public License Usage
Alternatively, this file may be used under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program 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. See the
GNU General Public License for more details.
A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
*/
#include "TimerEdit.h"
#include <cmath>
TimerEdit::TimerEdit(QWidget *parent) :
QLineEdit(parent),
m_showSeconds(true),
m_minTime(0),
m_maxTime(24 * 3600 - 1),
m_singleStep(1),
m_pageStep(60),
m_hourDigits(2),
m_validator(new QRegularExpressionValidator(this))
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
setupFormat();
setToolTip(tr("Use mouse scroll or UP/DOWN arrow keys to change time in small steps.\nCTRL + scroll or PAGE UP/DOWN keys to change time in larger steps."));
connect(this, &TimerEdit::textEdited, this, &TimerEdit::textEditedHandler);
}
int TimerEdit::timeInSeconds() const
{
int ret = 0;
ret += timepart(text(), TimeParts::Hours) * 3600;
ret += timepart(text(), TimeParts::Minutes) * 60;
ret += timepart(text(), TimeParts::Seconds);
ret *= timepart(text(), TimeParts::Polarity);
return ret;
}
int TimerEdit::timepart(const QString &input, TimerEdit::TimeParts part) const
{
int ret = 0;
QRegularExpressionMatch match = m_validator->regularExpression().match(input, 0, QRegularExpression::NormalMatch);
if (match.hasMatch()) {
switch (part) {
case Hours:
if (!match.captured("hrs").isEmpty())
ret = match.captured("hrs").toInt();
break;
case Minutes:
if (!match.captured("mins").isEmpty())
ret = match.captured("mins").toInt();
break;
case Seconds:
if (!match.captured("secs").isEmpty())
ret = match.captured("secs").toInt();
break;
case Polarity:
if (!match.captured("pol").isEmpty() && !match.captured("pol").compare("-"))
ret = -1;
else
ret = 1;
break;
default:
break;
}
}
return ret;
}
//// Slots ////
void TimerEdit::setTime(int seconds)
{
if (seconds > m_maxTime)
seconds = m_maxTime;
else if (seconds < m_minTime)
seconds = m_minTime;
div_t qrM = div(abs(seconds), 60);
div_t qrH = div(qrM.quot, 60);
QString val = QString("%1").arg(qrH.rem, 2, 10, QChar('0'));
if (m_hourDigits)
val.prepend(QString("%1:").arg(qrH.quot, m_hourDigits, 10, QChar('0')));
if (m_showSeconds)
val.append(QString(":%1").arg(qrM.rem, 2, 10, QChar('0')));
if (m_minTime < 0) {
if (seconds < 0)
val.prepend("-");
else
val.prepend(" ");
}
if (text().compare(val)) {
setText(val);
}
}
void TimerEdit::incrDecr(int seconds)
{
if (!seconds)
return;
const int cs = timeInSeconds();
int s = seconds + cs;
if (s < m_minTime)
s = m_minTime;
if (s > m_maxTime)
s = m_maxTime;
if (s != cs) {
setTime(s);
emitValueChanged();
}
}
void TimerEdit::setTimeRange(int minSeconds, int maxSeconds)
{
if (minSeconds <= maxSeconds && (m_minTime != minSeconds || m_maxTime != maxSeconds)) {
int digits = 0;
if (maxSeconds >= 3600)
digits = floorf(logf(round(maxSeconds / 3600)) / logf(10.0f)) + 1;
bool mod = (m_hourDigits != digits || std::signbit((float)m_minTime) != std::signbit((float)minSeconds));
m_minTime = minSeconds;
m_maxTime = maxSeconds;
m_hourDigits = digits;
if (mod)
setupFormat();
}
}
void TimerEdit::setMinimumTime(int minSeconds)
{
setTimeRange(minSeconds, (m_maxTime > minSeconds ? m_maxTime : minSeconds));
}
void TimerEdit::setMaximumTime(int maxSeconds)
{
setTimeRange((m_minTime < maxSeconds ? m_minTime : maxSeconds), maxSeconds);
}
void TimerEdit::setShowSeconds(bool showSeconds)
{
if (m_showSeconds != showSeconds) {
m_showSeconds = showSeconds;
setupFormat();
}
}
//// Protected ////
void TimerEdit::textEditedHandler()
{
int sec = timeInSeconds();
if (sec < m_minTime)
setTime(m_minTime);
else if (sec > m_maxTime)
setTime(m_maxTime);
else
return;
emitValueChanged();
}
void TimerEdit::setupFormat()
{
QString inputRe = "(?<mins>[0-5][0-9])";
QString inputMsk = "99";
QString suffx = "\\" % tr("m") % "\\" % tr("m");
if (m_hourDigits) {
inputRe.prepend("(?<hrs>[0-9]*[0-9]):");
inputMsk.prepend(":");
suffx.prepend(":");
for (int i=0; i < m_hourDigits; ++i) {
inputMsk.prepend("9");
suffx.prepend("\\" % tr("h"));
}
}
if (m_showSeconds) {
inputRe.append(":(?<secs>[0-5][0-9])");
inputMsk.append(":99");
suffx.append(":\\" % tr("s") % "\\" % tr("s"));
}
if (m_minTime < 0) {
inputRe.prepend("(?<pol>-|\\+|\\s)?");
inputMsk.prepend("#");
}
inputRe.prepend("^");
inputRe.append("\\s?\\[[^\\]]+\\]$");
inputMsk.append(" \\[" % suffx % "\\]");
m_validator->setRegularExpression(QRegularExpression(inputRe));
setInputMask(inputMsk);
setValidator(m_validator);
}
void TimerEdit::emitValueChanged()
{
emit QLineEdit::textEdited(text());
}
void TimerEdit::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Up:
incrDecr(m_singleStep);
break;
case Qt::Key_Down:
incrDecr(-(int)m_singleStep);
break;
case Qt::Key_PageUp:
incrDecr(m_pageStep);
break;
case Qt::Key_PageDown:
incrDecr(-(int)m_pageStep);
break;
case Qt::Key_Plus:
case Qt::Key_Equal:
case Qt::Key_Minus:
if (m_minTime < 0) {
setTime(-timeInSeconds());
emitValueChanged();
}
break;
default:
QLineEdit::keyPressEvent(event);
break;
}
}
void TimerEdit::wheelEvent(QWheelEvent *event)
{
if (event->angleDelta().isNull()) {
event->ignore();
return;
}
int numSteps = -event->angleDelta().y() / 8 / 15 * -1; // one step per 15deg
numSteps *= (event->modifiers() & Qt::ControlModifier) ? m_pageStep : m_singleStep;
incrDecr(numSteps);
event->accept();
}
@@ -1,103 +0,0 @@
/*
TimerEdit
https://github.com/mpaperno/maxLibQt
COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
Contact: http://www.WorldDesign.com/contact
LICENSE:
Commercial License Usage
Licensees holding valid commercial licenses may use this file in
accordance with the terms contained in a written agreement between
you and the copyright holder.
GNU General Public License Usage
Alternatively, this file may be used under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program 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. See the
GNU General Public License for more details.
A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
*/
#ifndef TIMEREDIT_H
#define TIMEREDIT_H
#include <QLineEdit>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QKeyEvent>
#include <QWheelEvent>
/**
\class TimerEdit
\version 1.0.0
\brief A time value line editor which accepts negative and large times (> 23:59:59), suitable for a timer, etc.
Qt's \c QTimeEdit has a limitation in that it can only accept "valid" times, in the range of 0:00:00 to 23:59:59.
The TimerEdit control aims to address that. It allows any amount of time to be entered, as well as negative times.
It provides a mask for data entry, a validator with settable min/max times, and increment/decrement via keyboard
or mouse wheel in configurable steps.
Data I/O is in seconds only, that is you set the time value in total seconds and also read it back in seconds
(there is no conversion from/to a \e QTime object).
*/
class TimerEdit : public QLineEdit
{
Q_OBJECT
Q_PROPERTY(int minimumTime READ minimumTime WRITE setMinimumTime)
Q_PROPERTY(int maximumTime READ maximumTime WRITE setMaximumTime)
Q_PROPERTY(bool showSeconds READ showSeconds WRITE setShowSeconds)
Q_PROPERTY(unsigned int singleStep READ singleStep WRITE setSingleStep)
Q_PROPERTY(unsigned int pageStep READ pageStep WRITE setPageStep)
public:
enum TimeParts {Whole, Polarity, Hours, Minutes, Seconds};
TimerEdit(QWidget *parent = Q_NULLPTR);
int timeInSeconds() const;
int timepart(const QString &input, TimeParts part) const;
bool showSeconds() const { return m_showSeconds; }
int minimumTime() const { return m_minTime; }
int maximumTime() const { return m_maxTime; }
unsigned int singleStep() const { return m_singleStep; }
unsigned int pageStep() const { return m_pageStep; }
public slots:
void setTime(int seconds);
void incrDecr(int seconds);
void setTimeRange(int minSeconds, int maxSeconds);
void setMinimumTime(int minSeconds);
void setMaximumTime(int maxSeconds);
void setShowSeconds(bool showSeconds);
void setSingleStep(unsigned int step) { m_singleStep = step; }
void setPageStep(unsigned int pageStep) { m_pageStep = pageStep; }
protected:
void textEditedHandler();
virtual void setupFormat();
void emitValueChanged();
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE;
bool m_showSeconds;
int m_minTime; // seconds
int m_maxTime; // seconds
unsigned m_singleStep; // seconds
unsigned m_pageStep; // seconds
short m_hourDigits;
QRegularExpressionValidator * m_validator;
};
#endif // TIMEREDIT_H