- initial import
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
This is my experiment to get:
|
||||
- simple app to make periodical breaks on my Kubuntu 20.04
|
||||
- learn Linux GUI apps packaging and distribution
|
||||
- avoid too heavy apps for simple task
|
||||
|
||||
Please don't blame on me for this app.
|
||||
My thanks for authors of similar apps ! You inspired me to make this one.
|
||||
|
||||
There is scripts/build_linux.sh script to produce AppImage file.
|
||||
Release part has ready one. But this was built on Kubuntu 20.04 and will require recent versions of runtime libraries.
|
||||
|
||||
Please send me questions and requests on dmytro.bogovych (at) gmail.com.
|
||||
|
||||
21
app/aboutdlg.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "aboutdlg.h"
|
||||
#include "ui_aboutdlg.h"
|
||||
#include "config.h"
|
||||
|
||||
AboutDlg::AboutDlg(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AboutDlg)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
auto version_text = QString("Version %1.%2.%3")
|
||||
.arg(QBREAK_VERSION_MAJOR)
|
||||
.arg(QBREAK_VERSION_MINOR)
|
||||
.arg(QBREAK_VERSION_SUFFIX);
|
||||
ui->mAppVersionLabel->setText(version_text);
|
||||
}
|
||||
|
||||
AboutDlg::~AboutDlg()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
22
app/aboutdlg.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef ABOUTDLG_H
|
||||
#define ABOUTDLG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class AboutDlg;
|
||||
}
|
||||
|
||||
class AboutDlg : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AboutDlg(QWidget *parent = nullptr);
|
||||
~AboutDlg();
|
||||
|
||||
private:
|
||||
Ui::AboutDlg *ui;
|
||||
};
|
||||
|
||||
#endif // ABOUTDLG_H
|
||||
133
app/aboutdlg.ui
Normal file
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AboutDlg</class>
|
||||
<widget class="QDialog" name="AboutDlg">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="mAppNameLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>QBreak.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="mAppVersionLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="mAppInfoLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>App to make breaks in work. Thanks to the authors of similar apps which inspired me to make this one.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AboutDlg</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AboutDlg</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
3
app/assets/images/README.txt
Normal file
@ -0,0 +1,3 @@
|
||||
URL for used image:
|
||||
https://thenounproject.com/icon/coffee-break-537907/
|
||||
|
||||
BIN
app/assets/images/app_icon.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
4
app/assets/images/app_icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m228 72c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm-782.25 360c-11.797 1.1133-21.801 12.148-21.75 24 0 304.82 77.43 453.77 174.75 624h-126.75c-13.297 0-24 10.703-24 24s10.703 24 24 24h864c13.297 0 24-10.703 24-24s-10.703-24-24-24h-126.75c25.375-44.387 49.617-87.379 71.25-132h115.5c46.531 0 84-37.469 84-84v-264c0-46.531-37.465-84-84-84h-13.125c0.69922-19.352 1.125-39.258 1.125-60 0-12.566-11.434-24-24-24h-912c-0.75-0.035156-1.5-0.035156-2.25 0zm27 48h862.5c-4.168 292.51-80.758 424.66-181.5 600h-499.5c-100.74-175.34-177.33-307.49-181.5-600zm907.5 84h15.75c24.383 0 36 11.621 36 36v264c0 24.383-11.617 36-36 36h-93.75c39.703-92.473 68.336-195.91 78-336z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/app_icon_dark.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/assets/images/app_icon_light.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
3
app/assets/images/coffee_cup/README.txt
Normal file
@ -0,0 +1,3 @@
|
||||
URL: https://ru.seaicons.com/89980/
|
||||
License: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
BIN
app/assets/images/coffee_cup/alecive-flatwoken-apps-caffeine.ico
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
app/assets/images/coffee_cup/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/assets/images/coffee_cup/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 674 B |
BIN
app/assets/images/coffee_cup/icon_24x24.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/assets/images/coffee_cup/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/assets/images/coffee_cup/icon_48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/assets/images/coffee_cup/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
app/assets/images/coffee_cup/icon_64x64.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/assets/images/coffee_cup/icon_72x72.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/assets/images/coffee_cup/icon_96x96.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
app/assets/images/noun-coffee-break-537907.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
4
app/assets/images/noun-coffee-break-537907.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m228 72c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm-782.25 360c-11.797 1.1133-21.801 12.148-21.75 24 0 304.82 77.43 453.77 174.75 624h-126.75c-13.297 0-24 10.703-24 24s10.703 24 24 24h864c13.297 0 24-10.703 24-24s-10.703-24-24-24h-126.75c25.375-44.387 49.617-87.379 71.25-132h115.5c46.531 0 84-37.469 84-84v-264c0-46.531-37.465-84-84-84h-13.125c0.69922-19.352 1.125-39.258 1.125-60 0-12.566-11.434-24-24-24h-912c-0.75-0.035156-1.5-0.035156-2.25 0zm27 48h862.5c-4.168 292.51-80.758 424.66-181.5 600h-499.5c-100.74-175.34-177.33-307.49-181.5-600zm907.5 84h15.75c24.383 0 36 11.621 36 36v264c0 24.383-11.617 36-36 36h-93.75c39.703-92.473 68.336-195.91 78-336z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
12
app/assets/misc/qbreak.desktop
Normal file
@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
GenericName=App to make the breaks
|
||||
Name=QBreak
|
||||
Version=0.0.5
|
||||
Exec=/usr/bin/qbreak
|
||||
Comment=App to make periodical breaks
|
||||
Icon=qbreak
|
||||
Type=Application
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
Encoding=UTF-8
|
||||
Categories=Utility;
|
||||
140
app/autostart.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
#include "autostart.h"
|
||||
#include <QDebug>
|
||||
|
||||
#if defined(TARGET_LINUX)
|
||||
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#define QBREAK_DESKTOP_NAME "qbreak.desktop"
|
||||
|
||||
static std::string read_desktop_file()
|
||||
{
|
||||
QFile f(":/assets/misc/qbreak.desktop");
|
||||
f.open(QFile::ReadOnly);
|
||||
auto bytes = f.readAll();
|
||||
return bytes.toStdString();
|
||||
}
|
||||
|
||||
static std::string fixup_desktop_file(const std::string& desktop_unit, const std::string& path)
|
||||
{
|
||||
// load set with first file
|
||||
std::istringstream inf(desktop_unit);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
for (unsigned int i=0; std::getline(inf,line); i++)
|
||||
{
|
||||
if (line.find("Exec=") == 0)
|
||||
{
|
||||
line = "Exec=" + path;
|
||||
}
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
std::copy(lines.begin(), lines.end(), std::ostream_iterator<std::string>(oss, "\n"));
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
static fs::path home_dir()
|
||||
{
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
return pw ? pw->pw_dir : std::string();
|
||||
}
|
||||
|
||||
static fs::path autostart_dir()
|
||||
{
|
||||
return home_dir() / ".config" / "autostart";
|
||||
}
|
||||
|
||||
static fs::path autostart_path()
|
||||
{
|
||||
return autostart_dir() / QBREAK_DESKTOP_NAME;
|
||||
}
|
||||
|
||||
void autostart::enable(const std::string& path_to_me)
|
||||
{
|
||||
// Put .desktop file to ~/.config/autostart
|
||||
std::ofstream ofs(autostart_path());
|
||||
if (ofs.is_open())
|
||||
{
|
||||
ofs << fixup_desktop_file(read_desktop_file(), path_to_me);
|
||||
ofs.close();
|
||||
}
|
||||
else
|
||||
qDebug() << "Failed to write the desktop entry into autostart dir. Error: " << errno;
|
||||
}
|
||||
|
||||
void autostart::disable()
|
||||
{
|
||||
// Remove .desktop file from ~/.config/autostart
|
||||
::remove(autostart_path().c_str());
|
||||
}
|
||||
|
||||
bool autostart::is_enabled()
|
||||
{
|
||||
return fs::exists(fs::path(autostart_path()));
|
||||
}
|
||||
|
||||
// ----- appmenu -----
|
||||
static fs::path appmenu_install_dir()
|
||||
{
|
||||
// Global one
|
||||
// return fs::path("/usr/share/applications");
|
||||
|
||||
// User only
|
||||
return home_dir() / ".local" / "share" / "applications";
|
||||
}
|
||||
|
||||
void appmenu::install(const std::string& path_to_me)
|
||||
{
|
||||
// Put .desktop file to ~/.config/autostart
|
||||
std::ofstream ofs(appmenu_install_dir() / QBREAK_DESKTOP_NAME);
|
||||
if (ofs.is_open())
|
||||
{
|
||||
ofs << fixup_desktop_file(read_desktop_file(), path_to_me);
|
||||
ofs.close();
|
||||
}
|
||||
else
|
||||
qDebug() << "Failed to write the desktop entry into app menu dir. Error: " << errno;
|
||||
|
||||
// Install latest icons
|
||||
{
|
||||
auto icons = {"16x16", "32x32", "64x64", "128x128", "512x512"};
|
||||
|
||||
// Here Qt part
|
||||
auto target_dir = QFileInfo(QDir::homePath() + "/.local/share/icons/hicolor").absoluteFilePath();
|
||||
if (QFileInfo::exists(target_dir))
|
||||
{
|
||||
// Copy icons from resources
|
||||
for (auto& icon_suffix: icons)
|
||||
{
|
||||
QString icon_src = QString(":/assets/images/coffee_cup/icon_") + icon_suffix + ".png",
|
||||
icon_dst = target_dir + "/" + icon_suffix + "/apps/qbreak.png";
|
||||
QFile::copy(icon_src, icon_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void appmenu::uninstall()
|
||||
{
|
||||
if (is_installed())
|
||||
fs::remove(appmenu_install_dir() / QBREAK_DESKTOP_NAME);
|
||||
}
|
||||
|
||||
bool appmenu::is_installed()
|
||||
{
|
||||
return fs::exists(appmenu_install_dir() / QBREAK_DESKTOP_NAME);
|
||||
}
|
||||
#endif
|
||||
22
app/autostart.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef AUTOSTART_H
|
||||
#define AUTOSTART_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class autostart
|
||||
{
|
||||
public:
|
||||
static void enable(const std::string& path_to_me);
|
||||
static void disable();
|
||||
static bool is_enabled();
|
||||
};
|
||||
|
||||
class appmenu
|
||||
{
|
||||
public:
|
||||
static void install(const std::string& path_to_me);
|
||||
static void uninstall();
|
||||
static bool is_installed();
|
||||
};
|
||||
|
||||
#endif // AUTOSTART_H
|
||||
9
app/config.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
// App version
|
||||
#define QBREAK_VERSION_MAJOR 0
|
||||
#define QBREAK_VERSION_MINOR 0
|
||||
#define QBREAK_VERSION_SUFFIX 5
|
||||
|
||||
#endif // CONFIG_H
|
||||
57
app/main.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include "mainwindow.h"
|
||||
#include "autostart.h"
|
||||
#include "runguard.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QTranslator>
|
||||
#include <QFileInfo>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
RunGuard guard("adjfaifaif");
|
||||
if ( !guard.tryToRun() )
|
||||
return 0;
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QCoreApplication::setOrganizationName("qbreak.com");
|
||||
QCoreApplication::setOrganizationDomain("qbreak.com");
|
||||
QCoreApplication::setApplicationName("QBreak");
|
||||
|
||||
QTranslator translator;
|
||||
bool ok = translator.load(":/i18n/strings_" + QLocale::system().name());
|
||||
|
||||
if (ok)
|
||||
app.installTranslator(&translator);
|
||||
|
||||
app.setQuitOnLastWindowClosed(false);
|
||||
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption test_1(QStringList() << "t1" << "test_1");
|
||||
parser.addOption(test_1);
|
||||
|
||||
// Run app with break intervals & duration 60 seconds.
|
||||
// This should trigger the notification in 30 seconds before breaks.
|
||||
QCommandLineOption test_2(QStringList() << "t2" << "test_2");
|
||||
parser.addOption(test_2);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
// Put itself into app menu
|
||||
appmenu::install(QFileInfo(argv[0]).absoluteFilePath().toStdString());
|
||||
|
||||
// Main window is full screen window, so start with tray icon only
|
||||
MainWindow w;
|
||||
w.hide();
|
||||
|
||||
if (parser.isSet(test_1))
|
||||
w.test_1();
|
||||
else
|
||||
if (parser.isSet(test_2))
|
||||
w.test_2();
|
||||
|
||||
int retcode = app.exec();
|
||||
|
||||
return retcode;
|
||||
}
|
||||
379
app/mainwindow.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settings.h"
|
||||
#include "autostart.h"
|
||||
#include "aboutdlg.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
#include <QSvgGenerator>
|
||||
#include <QPalette>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QFileInfo>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
init();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (mSettingsDialog)
|
||||
delete mSettingsDialog;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
static bool isDarkTheme()
|
||||
{
|
||||
// Thanks to stackoverflow ! Another idea is to use https://successfulsoftware.net/2021/03/31/how-to-add-a-dark-theme-to-your-qt-application/
|
||||
auto label = QLabel("am I in the dark?");
|
||||
auto text_hsv_value = label.palette().color(QPalette::WindowText).value();
|
||||
auto bg_hsv_value = label.palette().color(QPalette::Background).value();
|
||||
bool dark_theme_found = text_hsv_value > bg_hsv_value;
|
||||
|
||||
return dark_theme_found;
|
||||
}
|
||||
|
||||
QIcon MainWindow::getAppIcon()
|
||||
{
|
||||
QIcon app_icon(QPixmap(QString(":/assets/images/coffee_cup/icon_128x128.png")));
|
||||
|
||||
return app_icon;
|
||||
}
|
||||
|
||||
QIcon MainWindow::getTrayIcon()
|
||||
{
|
||||
// QString app_icon_name = isDarkTheme() ? "app_icon_dark.png" : "app_icon_light.png";
|
||||
// QIcon app_icon(QPixmap(QString(":/assets/images/%1").arg(app_icon_name)));
|
||||
// return app_icon;
|
||||
|
||||
return QIcon(QPixmap(":/assets/images/coffee_cup/icon_64x64.png"));
|
||||
}
|
||||
|
||||
void MainWindow::init()
|
||||
{
|
||||
// Icon
|
||||
setWindowIcon(getAppIcon());
|
||||
|
||||
// ToDo: finish the multiple monitor support
|
||||
/*QDesktopWidget *widget = QApplication::desktop();
|
||||
qDebug() << "Number of screens:" << widget->screenCount();
|
||||
|
||||
connect(widget, &QDesktopWidget::screenCountChanged, this, [](int screenCount){});
|
||||
if (widget->screenCount() > 1) {
|
||||
} else if (widget->screenCount() == 1){
|
||||
}*/
|
||||
|
||||
// Tray icon
|
||||
createTrayIcon();
|
||||
|
||||
// Latest config
|
||||
loadConfig();
|
||||
|
||||
// Settings dialog
|
||||
mSettingsDialog = nullptr;
|
||||
|
||||
// Timer
|
||||
mTimer = new QTimer(this);
|
||||
mTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||
mTimer->setSingleShot(true);
|
||||
connect(mTimer, SIGNAL(timeout()), this, SLOT(onLongBreakStart()));
|
||||
|
||||
mNotifyTimer = new QTimer(this);
|
||||
mNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||
mNotifyTimer->setSingleShot(true);
|
||||
connect(mNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify()));
|
||||
|
||||
|
||||
mUpdateUITimer = new QTimer(this);
|
||||
mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||
mUpdateUITimer->setSingleShot(false);
|
||||
mUpdateUITimer->setInterval(std::chrono::minutes(1));
|
||||
connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI()));
|
||||
mUpdateUITimer->start();
|
||||
|
||||
mProgressTimer = new QTimer(this);
|
||||
mProgressTimer->setInterval(std::chrono::milliseconds(1000));
|
||||
mProgressTimer->setSingleShot(false);
|
||||
connect(mProgressTimer, SIGNAL(timeout()), this, SLOT(onProgress()));
|
||||
|
||||
connect(ui->mPostponeButton, SIGNAL(clicked()), this, SLOT(onLongBreakPostpone()));
|
||||
|
||||
// Use the latest config
|
||||
applyConfig();
|
||||
|
||||
// Refresh UI
|
||||
onUpdateUI();
|
||||
}
|
||||
|
||||
void MainWindow::loadConfig()
|
||||
{
|
||||
app_settings settings;
|
||||
mAppConfig = settings.load();
|
||||
}
|
||||
|
||||
void MainWindow::applyConfig()
|
||||
{
|
||||
if (mTimer)
|
||||
{
|
||||
if (mTimer->interval() != mAppConfig.longbreak_interval)
|
||||
{
|
||||
mTimer->stop();
|
||||
mTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval));
|
||||
mTimer->start();
|
||||
|
||||
mNotifyTimer->stop();
|
||||
mNotifyTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
||||
mNotifyTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (mAppConfig.window_on_top)
|
||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
else
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
|
||||
|
||||
if (mAppConfig.autostart)
|
||||
{
|
||||
QString path_to_me = QFileInfo(QApplication::arguments().front()).canonicalPath();
|
||||
autostart::enable(path_to_me.toStdString());
|
||||
}
|
||||
else
|
||||
autostart::disable();
|
||||
}
|
||||
|
||||
void MainWindow::schedule()
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void MainWindow::test_1()
|
||||
{
|
||||
// 10 seconds test break
|
||||
mAppConfig.longbreak_length = 10;
|
||||
mAppConfig.longbreak_postpone_interval = 10;
|
||||
mAppConfig.longbreak_interval = 10;
|
||||
|
||||
mAppConfig.window_on_top = true;
|
||||
mAppConfig.verbose = true;
|
||||
|
||||
onLongBreakStart();
|
||||
}
|
||||
|
||||
void MainWindow::test_2()
|
||||
{
|
||||
// 60 seconds test break
|
||||
mAppConfig.longbreak_length = 60;
|
||||
mAppConfig.longbreak_postpone_interval = 60;
|
||||
mAppConfig.longbreak_interval = 60;
|
||||
|
||||
mAppConfig.window_on_top = true;
|
||||
mAppConfig.verbose = true;
|
||||
|
||||
applyConfig();
|
||||
}
|
||||
|
||||
void MainWindow::showMe()
|
||||
{
|
||||
// Show on primary screen
|
||||
QScreen* screen = QGuiApplication::primaryScreen();
|
||||
showFullScreen();
|
||||
if (windowHandle())
|
||||
{
|
||||
windowHandle()->setScreen(screen);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::hideMe()
|
||||
{
|
||||
this->hide();
|
||||
}
|
||||
|
||||
static QString secondsToText(int seconds)
|
||||
{
|
||||
if (seconds < 60)
|
||||
return QString("%1s").arg(seconds);
|
||||
else
|
||||
return QString("%1m %2s").arg(seconds / 60).arg(seconds % 60);
|
||||
}
|
||||
|
||||
void MainWindow::createTrayIcon()
|
||||
{
|
||||
mTrayIcon = new QSystemTrayIcon(this);
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
QAction* nextBreakAction = new QAction(tr("Start next break"), menu);
|
||||
connect(nextBreakAction, SIGNAL(triggered()), this, SLOT(onNextBreak()));
|
||||
|
||||
QAction* settingsAction = new QAction(tr("Settings"), menu);
|
||||
connect(settingsAction, SIGNAL(triggered()), this, SLOT(onSettings()));
|
||||
|
||||
QAction* aboutAction = new QAction(tr("About"), menu);
|
||||
connect(aboutAction, SIGNAL(triggered()), this, SLOT(onAbout()));
|
||||
|
||||
QAction* exitAction = new QAction(tr("Exit"), menu);
|
||||
connect(exitAction, SIGNAL(triggered()), this, SLOT(onExit()));
|
||||
|
||||
menu->addAction(nextBreakAction);
|
||||
menu->addAction(settingsAction);
|
||||
menu->addAction(aboutAction);
|
||||
menu->addAction(exitAction);
|
||||
|
||||
mTrayIcon->setContextMenu(menu);
|
||||
mTrayIcon->setIcon(getTrayIcon());
|
||||
mTrayIcon->setToolTip(AppName);
|
||||
mTrayIcon->show();
|
||||
}
|
||||
|
||||
void MainWindow::onUpdateUI()
|
||||
{
|
||||
if (mTrayIcon)
|
||||
{
|
||||
if (mProgressTimer->isActive())
|
||||
{
|
||||
// Break is in effect now
|
||||
mTrayIcon->setToolTip(QString());
|
||||
}
|
||||
else
|
||||
if (mTimer->isActive())
|
||||
{
|
||||
auto remaining = std::chrono::duration_cast<std::chrono::minutes>(mTimer->remainingTimeAsDuration());
|
||||
if (remaining.count() == 0)
|
||||
mTrayIcon->setToolTip(tr("Less than a minute left until the next break."));
|
||||
else
|
||||
mTrayIcon->setToolTip(tr("There are %1 minutes left until the next break.").arg(remaining.count()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onLongBreakNotify()
|
||||
{
|
||||
mTrayIcon->showMessage(tr("New break"),
|
||||
tr("New break will start in %1 secs").arg(Default_Notify_Length),
|
||||
getAppIcon(),
|
||||
30000);
|
||||
}
|
||||
|
||||
void MainWindow::onLongBreakStart()
|
||||
{
|
||||
qDebug() << "Long break starts for " << secondsToText(mAppConfig.longbreak_postpone_interval);
|
||||
|
||||
ui->mPostponeButton->setText(tr("Postpone for ") + secondsToText(mAppConfig.longbreak_postpone_interval));
|
||||
showMe();
|
||||
|
||||
mBreakStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Start progress bar
|
||||
mProgressTimer->start();
|
||||
|
||||
// Refresh UI
|
||||
onUpdateUI();
|
||||
}
|
||||
|
||||
void MainWindow::onLongBreakEnd()
|
||||
{
|
||||
qDebug() << "Long break ends.";
|
||||
|
||||
// Prepare to next triggering
|
||||
ui->mProgressBar->setValue(0);
|
||||
|
||||
// Hide the window
|
||||
hideMe();
|
||||
|
||||
// Start new timer
|
||||
mTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval));
|
||||
mNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
||||
|
||||
// Refresh UI
|
||||
onUpdateUI();
|
||||
}
|
||||
|
||||
void MainWindow::onLongBreakPostpone()
|
||||
{
|
||||
qDebug() << "Long break postponed.";
|
||||
hideMe();
|
||||
|
||||
mProgressTimer->stop();
|
||||
ui->mProgressBar->setValue(0);
|
||||
|
||||
// Start timer again
|
||||
mTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval));
|
||||
mNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval - 30));
|
||||
|
||||
// Refresh UI
|
||||
onUpdateUI();
|
||||
}
|
||||
|
||||
void MainWindow::onProgress()
|
||||
{
|
||||
auto timestamp = std::chrono::steady_clock::now();
|
||||
auto delta = std::chrono::duration_cast<std::chrono::seconds>(timestamp - mBreakStartTime).count();
|
||||
|
||||
int percents = (delta / float(mAppConfig.longbreak_length) * 100);
|
||||
ui->mProgressBar->setValue(percents);
|
||||
int remaining = mAppConfig.longbreak_length - delta;
|
||||
if (remaining < 0)
|
||||
remaining = 0;
|
||||
|
||||
ui->mRemainingLabel->setText(QString("Remaining: ") + secondsToText(remaining));
|
||||
|
||||
if (percents > 100)
|
||||
{
|
||||
mProgressTimer->stop();
|
||||
onLongBreakEnd();
|
||||
}
|
||||
else
|
||||
showMe();
|
||||
}
|
||||
|
||||
void MainWindow::onNextBreak()
|
||||
{
|
||||
mTimer->stop();
|
||||
mNotifyTimer->stop();
|
||||
|
||||
onLongBreakStart();
|
||||
}
|
||||
|
||||
void MainWindow::onSettings()
|
||||
{
|
||||
if (mSettingsDialog == nullptr)
|
||||
{
|
||||
// Settings dialog must be top level window to be positioned on the primary screen
|
||||
mSettingsDialog = new SettingsDialog(nullptr);
|
||||
connect(mSettingsDialog, &QDialog::accepted, [this]()
|
||||
{
|
||||
mAppConfig = app_settings::load();
|
||||
applyConfig();
|
||||
mSettingsDialog->hide();
|
||||
});
|
||||
|
||||
connect(mSettingsDialog, &QDialog::rejected, [this]()
|
||||
{
|
||||
mSettingsDialog->hide();
|
||||
});
|
||||
}
|
||||
// Move to primary screen
|
||||
mSettingsDialog->show();
|
||||
QScreen* screen = QGuiApplication::primaryScreen();
|
||||
mSettingsDialog->windowHandle()->setScreen(screen);
|
||||
}
|
||||
|
||||
void MainWindow::onAbout()
|
||||
{
|
||||
AboutDlg dlg;
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::onExit()
|
||||
{
|
||||
this->close();
|
||||
QApplication::exit();
|
||||
}
|
||||
67
app/mainwindow.h
Normal file
@ -0,0 +1,67 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include <chrono>
|
||||
#include "settings.h"
|
||||
#include "settingsdialog.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class MainWindow; }
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
// Start screen immediately with 15 seconds interval
|
||||
void test_1();
|
||||
|
||||
// Run 60/60 seconds interval & duration
|
||||
void test_2();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
QTimer* mTimer;
|
||||
QTimer* mNotifyTimer;
|
||||
QTimer* mShowNotifyTimer;
|
||||
QTimer* mUpdateUITimer;
|
||||
QTimer* mProgressTimer;
|
||||
QSystemTrayIcon* mTrayIcon;
|
||||
SettingsDialog* mSettingsDialog;
|
||||
std::chrono::steady_clock::time_point mBreakStartTime;
|
||||
app_settings::config mAppConfig;
|
||||
|
||||
void init();
|
||||
void loadConfig();
|
||||
void applyConfig();
|
||||
void schedule();
|
||||
void createTrayIcon();
|
||||
void showMe();
|
||||
void hideMe();
|
||||
|
||||
void startNotification();
|
||||
|
||||
QIcon getAppIcon();
|
||||
QIcon getTrayIcon();
|
||||
|
||||
public slots:
|
||||
void onUpdateUI();
|
||||
void onLongBreakNotify();
|
||||
void onLongBreakStart();
|
||||
void onLongBreakPostpone();
|
||||
void onLongBreakEnd();
|
||||
void onProgress();
|
||||
void onNextBreak();
|
||||
void onSettings();
|
||||
void onAbout();
|
||||
void onExit();
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
143
app/mainwindow.ui
Normal file
@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
|
||||
<widget class="QProgressBar" name="mProgressBar">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="mRemainingLabel">
|
||||
<property name="text">
|
||||
<string notr="true">Remaining:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QPushButton" name="mPostponeButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Postpone for 10 minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
43
app/qbreak.pro
Normal file
@ -0,0 +1,43 @@
|
||||
QT += core gui svg
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
CONFIG += c++17 lrelease embed_translations
|
||||
|
||||
# You can make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
aboutdlg.cpp \
|
||||
autostart.cpp \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
settings.cpp \
|
||||
settingsdialog.cpp \
|
||||
runguard.cpp
|
||||
|
||||
HEADERS += \
|
||||
aboutdlg.h \
|
||||
autostart.h \
|
||||
config.h \
|
||||
mainwindow.h \
|
||||
settings.h \
|
||||
settingsdialog.h \
|
||||
runguard.h
|
||||
|
||||
FORMS += \
|
||||
aboutdlg.ui \
|
||||
mainwindow.ui \
|
||||
settingsdialog.ui
|
||||
|
||||
RESOURCES = qbreak.qrc
|
||||
|
||||
TRANSLATIONS = strings.ts strings_en.ts strings_ru.ts
|
||||
|
||||
unix:!macx: DEFINES += TARGET_LINUX
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
16
app/qbreak.qrc
Normal file
@ -0,0 +1,16 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>assets/images/app_icon_dark.png</file>
|
||||
<file>assets/images/app_icon_light.png</file>
|
||||
<file>assets/misc/qbreak.desktop</file>
|
||||
<file>assets/images/coffee_cup/icon_16x16.png</file>
|
||||
<file>assets/images/coffee_cup/icon_24x24.png</file>
|
||||
<file>assets/images/coffee_cup/icon_32x32.png</file>
|
||||
<file>assets/images/coffee_cup/icon_48x48.png</file>
|
||||
<file>assets/images/coffee_cup/icon_64x64.png</file>
|
||||
<file>assets/images/coffee_cup/icon_72x72.png</file>
|
||||
<file>assets/images/coffee_cup/icon_96x96.png</file>
|
||||
<file>assets/images/coffee_cup/icon_128x128.png</file>
|
||||
<file>assets/images/coffee_cup/icon_512x512.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
79
app/runguard.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include "runguard.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString generateKeyHash( const QString& key, const QString& salt )
|
||||
{
|
||||
QByteArray data;
|
||||
|
||||
data.append( key.toUtf8() );
|
||||
data.append( salt.toUtf8() );
|
||||
data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
RunGuard::RunGuard( const QString& key )
|
||||
: key( key )
|
||||
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
|
||||
, sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
|
||||
, sharedMem( sharedmemKey )
|
||||
, memLock( memLockKey, 1 )
|
||||
{
|
||||
memLock.acquire();
|
||||
{
|
||||
QSharedMemory fix( sharedmemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
|
||||
fix.attach();
|
||||
}
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
RunGuard::~RunGuard()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
bool RunGuard::isAnotherRunning()
|
||||
{
|
||||
if ( sharedMem.isAttached() )
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool isRunning = sharedMem.attach();
|
||||
if ( isRunning )
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
bool RunGuard::tryToRun()
|
||||
{
|
||||
if ( isAnotherRunning() ) // Extra check
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool result = sharedMem.create( sizeof( quint64 ) );
|
||||
memLock.release();
|
||||
if ( !result )
|
||||
{
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RunGuard::release()
|
||||
{
|
||||
memLock.acquire();
|
||||
if ( sharedMem.isAttached() )
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
}
|
||||
32
app/runguard.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef RUNGUARD_H
|
||||
#define RUNGUARD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
|
||||
|
||||
class RunGuard
|
||||
{
|
||||
|
||||
public:
|
||||
RunGuard( const QString& key );
|
||||
~RunGuard();
|
||||
|
||||
bool isAnotherRunning();
|
||||
bool tryToRun();
|
||||
void release();
|
||||
|
||||
private:
|
||||
const QString key;
|
||||
const QString memLockKey;
|
||||
const QString sharedmemKey;
|
||||
|
||||
QSharedMemory sharedMem;
|
||||
QSystemSemaphore memLock;
|
||||
|
||||
Q_DISABLE_COPY( RunGuard )
|
||||
};
|
||||
|
||||
|
||||
#endif // RUNGUARD_H
|
||||
37
app/settings.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
// Key names for settings
|
||||
const QString Key_LongBreak_Interval = "LongBreak_Interval";
|
||||
const QString Key_LongBreak_PostponeInterval = "LongBreak_Postpone_Interval";
|
||||
const QString Key_LongBreak_Length = "LongBreak_Length";
|
||||
const QString Key_Window_On_Top = "Window_On_Top";
|
||||
const QString Key_Verbose = "Verbose";
|
||||
const QString Key_Autostart = "Autostart";
|
||||
|
||||
void app_settings::save(const config &cfg)
|
||||
{
|
||||
QSettings s;
|
||||
|
||||
s.setValue(Key_LongBreak_Interval, cfg.longbreak_interval);
|
||||
s.setValue(Key_LongBreak_Length, cfg.longbreak_length);
|
||||
s.setValue(Key_LongBreak_PostponeInterval, cfg.longbreak_postpone_interval);
|
||||
s.setValue(Key_Window_On_Top, cfg.window_on_top);
|
||||
s.setValue(Key_Verbose, cfg.verbose);
|
||||
s.setValue(Key_Autostart, cfg.autostart);
|
||||
}
|
||||
|
||||
app_settings::config app_settings::load()
|
||||
{
|
||||
QSettings s;
|
||||
config r;
|
||||
|
||||
r.longbreak_interval = s.value(Key_LongBreak_Interval, Default_LongBreak_Interval).toInt();
|
||||
r.longbreak_length = s.value(Key_LongBreak_Length, Default_LongBreak_Length).toInt();
|
||||
r.longbreak_postpone_interval = s.value(Key_LongBreak_PostponeInterval, Default_LongBreak_PostponeInterval).toInt();
|
||||
r.window_on_top = s.value(Key_Window_On_Top, Default_Autostart).toBool();
|
||||
r.verbose = s.value(Key_Verbose, Default_Verbose).toBool();
|
||||
r.autostart = s.value(Key_Autostart, Default_Autostart).toBool();
|
||||
return r;
|
||||
}
|
||||
44
app/settings.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
// Default values in seconds
|
||||
const int Default_LongBreak_Interval = 50 * 60;
|
||||
const int Default_LongBreak_PostponeInterval = 10 * 60;
|
||||
const int Default_LongBreak_Length = 10 * 60;
|
||||
const int Default_Notify_Length = 30;
|
||||
|
||||
// Is break window is running on the top ?
|
||||
const bool Default_WindowOnTop = true;
|
||||
|
||||
// Is verbose debug output
|
||||
const bool Default_Verbose = false;
|
||||
|
||||
// Default autostart
|
||||
const bool Default_Autostart = true;
|
||||
|
||||
|
||||
// Used app name
|
||||
const QString AppName = "QBreak";
|
||||
|
||||
class app_settings
|
||||
{
|
||||
public:
|
||||
struct config
|
||||
{
|
||||
// Seconds
|
||||
int longbreak_interval = Default_LongBreak_Interval;
|
||||
int longbreak_postpone_interval = Default_LongBreak_PostponeInterval;
|
||||
int longbreak_length = Default_LongBreak_Length;
|
||||
bool window_on_top = Default_WindowOnTop;
|
||||
bool verbose = Default_Verbose;
|
||||
bool autostart = Default_Autostart;
|
||||
};
|
||||
|
||||
static void save(const config& cfg);
|
||||
static config load();
|
||||
};
|
||||
|
||||
|
||||
#endif // SETTINGS_H
|
||||
47
app/settingsdialog.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "settingsdialog.h"
|
||||
#include "ui_settingsdialog.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <QToolTip>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
const QString ConversionError = "Integer value expected.";
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
init();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SettingsDialog::init()
|
||||
{
|
||||
setWindowTitle("Settings");
|
||||
|
||||
auto c = app_settings::load();
|
||||
ui->mAutostartCheckbox->setChecked(c.autostart);
|
||||
ui->mWindowOnTopCheckbox->setChecked(c.window_on_top);
|
||||
ui->mBreakIntervalEdit->setText(QString::number(c.longbreak_interval / 60));
|
||||
ui->mBreakDurationEdit->setText(QString::number(c.longbreak_length / 60));
|
||||
ui->mPostponeTimeEdit->setText(QString::number(c.longbreak_postpone_interval / 60));
|
||||
}
|
||||
|
||||
void SettingsDialog::accept()
|
||||
{
|
||||
auto c = app_settings::load();
|
||||
c.autostart = ui->mAutostartCheckbox->isChecked();
|
||||
c.window_on_top = ui->mWindowOnTopCheckbox->isChecked();
|
||||
c.longbreak_interval = ui->mBreakIntervalEdit->text().toInt() * 60;
|
||||
c.longbreak_length = ui->mBreakDurationEdit->text().toInt() * 60;
|
||||
c.longbreak_postpone_interval = ui->mPostponeTimeEdit->text().toInt() * 60;
|
||||
app_settings::save(c);
|
||||
|
||||
emit accepted();
|
||||
}
|
||||
27
app/settingsdialog.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef SETTINGSDIALOG_H
|
||||
#define SETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class SettingsDialog;
|
||||
}
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||
~SettingsDialog();
|
||||
|
||||
private:
|
||||
Ui::SettingsDialog *ui;
|
||||
|
||||
void init();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
156
app/settingsdialog.ui
Normal file
@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SettingsDialog</class>
|
||||
<widget class="QDialog" name="SettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>746</width>
|
||||
<height>167</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>746</width>
|
||||
<height>167</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="leftMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Autostart</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="mAutostartCheckbox">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Window on top</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="mWindowOnTopCheckbox">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Break interval (minutes)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="mBreakIntervalEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Break duration (minutes)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="mBreakDurationEdit"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Postpone time (minutes)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="mPostponeTimeEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
113
app/strings.ts
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US" sourcelanguage="en">
|
||||
<context>
|
||||
<name>AboutDlg</name>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="20"/>
|
||||
<source>Dialog</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="32"/>
|
||||
<source>QBreak.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="51"/>
|
||||
<source>Version</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="70"/>
|
||||
<source>App to make breaks in work. Thanks to the authors of similar apps which inspired me to make this one.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="mainwindow.ui" line="14"/>
|
||||
<source>MainWindow</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="213"/>
|
||||
<source>Start next break</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="216"/>
|
||||
<source>Settings</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="219"/>
|
||||
<source>About</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="222"/>
|
||||
<source>Exit</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="250"/>
|
||||
<source>Less than a minute left until the next break.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="252"/>
|
||||
<source>There are %1 minutes left until the next break.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="259"/>
|
||||
<source>New break</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="260"/>
|
||||
<source>New break will start in %1 secs</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="269"/>
|
||||
<source>Postpone for </source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="26"/>
|
||||
<source>Dialog</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="52"/>
|
||||
<source>Autostart</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="66"/>
|
||||
<source>Window on top</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="80"/>
|
||||
<source>Break interval (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="90"/>
|
||||
<source>Break duration (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="100"/>
|
||||
<source>Postpone time (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
113
app/strings_en.ts
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US" sourcelanguage="en">
|
||||
<context>
|
||||
<name>AboutDlg</name>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="20"/>
|
||||
<source>Dialog</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="32"/>
|
||||
<source>QBreak.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="51"/>
|
||||
<source>Version</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="70"/>
|
||||
<source>App to make breaks in work. Thanks to the authors of similar apps which inspired me to make this one.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="mainwindow.ui" line="14"/>
|
||||
<source>MainWindow</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="213"/>
|
||||
<source>Start next break</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="216"/>
|
||||
<source>Settings</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="219"/>
|
||||
<source>About</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="222"/>
|
||||
<source>Exit</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="250"/>
|
||||
<source>Less than a minute left until the next break.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="252"/>
|
||||
<source>There are %1 minutes left until the next break.</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="259"/>
|
||||
<source>New break</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="260"/>
|
||||
<source>New break will start in %1 secs</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="269"/>
|
||||
<source>Postpone for </source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="26"/>
|
||||
<source>Dialog</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="52"/>
|
||||
<source>Autostart</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="66"/>
|
||||
<source>Window on top</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="80"/>
|
||||
<source>Break interval (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="90"/>
|
||||
<source>Break duration (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="100"/>
|
||||
<source>Postpone time (minutes)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
125
app/strings_ru.ts
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="ru" sourcelanguage="en">
|
||||
<context>
|
||||
<name>AboutDlg</name>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="20"/>
|
||||
<source>Dialog</source>
|
||||
<translation>О программе</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="32"/>
|
||||
<source>QBreak.</source>
|
||||
<translation>QBreak.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="51"/>
|
||||
<source>Version</source>
|
||||
<translation>Версия</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="aboutdlg.ui" line="70"/>
|
||||
<source>App to make breaks in work. Thanks to the authors of similar apps which inspired me to make this one.</source>
|
||||
<translation>Приложение чтобы устраивать себе перерывы в работе. Спасибо авторам аналогичных приложений которые вдохновили меня сделать это приложение.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="mainwindow.ui" line="14"/>
|
||||
<source>MainWindow</source>
|
||||
<translation>QBreak</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="213"/>
|
||||
<source>Start next break</source>
|
||||
<translation>Начать перерыв</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="216"/>
|
||||
<source>Settings</source>
|
||||
<translation>Настройки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="219"/>
|
||||
<source>About</source>
|
||||
<translation>О программе</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="222"/>
|
||||
<source>Exit</source>
|
||||
<translation>Выход</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="250"/>
|
||||
<source>Less than a minute left until the next break.</source>
|
||||
<translation>До перерыва осталось меньше минуты.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="252"/>
|
||||
<source>There are %1 minutes left until the next break.</source>
|
||||
<translation>Осталось %1 минут до перерыва.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="259"/>
|
||||
<source>New break</source>
|
||||
<translation>Скоро перерыв</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="260"/>
|
||||
<source>New break will start in %1 secs</source>
|
||||
<translation>Перерыв начнется через %1 секунд.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="mainwindow.cpp" line="269"/>
|
||||
<source>Postpone for </source>
|
||||
<translation>Отложить на</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="26"/>
|
||||
<source>Dialog</source>
|
||||
<translation>Настройки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="52"/>
|
||||
<source>Autostart</source>
|
||||
<translation>Автозапуск</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="66"/>
|
||||
<source>Window on top</source>
|
||||
<translation>Показывать поверх всех окон</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="80"/>
|
||||
<source>Break interval (minutes)</source>
|
||||
<translation>Промежуток между перерывами (в минутах)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="90"/>
|
||||
<source>Break duration (minutes)</source>
|
||||
<translation>Длина перерывов (в минутах)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="settingsdialog.ui" line="100"/>
|
||||
<source>Postpone time (minutes)</source>
|
||||
<translation>Насколько можно отложить перерыв (в минутах)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Break interval (seconds)</source>
|
||||
<translation type="vanished">Промежуток между перерывами в секундах</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Break duration (seconds)</source>
|
||||
<translation type="vanished">Длина перерыва в секундах</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Postpone time (seconds)</source>
|
||||
<translation type="vanished">На сколько можно отложить перерыв (в секундах)</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
BIN
scripts/appimage_dir.tar.gz
Normal file
5
scripts/build_linux.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#export QTDIR=/home/$USER/qt/5.12.10/gcc_64
|
||||
export QTDIR=/home/$USER/qt5/5.12.12/gcc_64
|
||||
|
||||
/usr/bin/python3 build_qbreak.py
|
||||
|
||||
93
scripts/build_qbreak.py
Executable file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import typing
|
||||
import platform
|
||||
import os
|
||||
import shutil
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import multiprocessing
|
||||
import build_utils
|
||||
|
||||
EXIT_OK = 0
|
||||
EXIT_ERROR = 1
|
||||
|
||||
# Check if Qt is specified
|
||||
if not 'QTDIR' in os.environ:
|
||||
print('Qt location must be set in QTDIR environment variable.')
|
||||
exit(1)
|
||||
|
||||
# Prepare build directory
|
||||
build_dir = Path('build')
|
||||
if build_dir.exists():
|
||||
shutil.rmtree(build_dir)
|
||||
os.mkdir(build_dir)
|
||||
|
||||
app_source = Path('../app').resolve()
|
||||
version_suffix = build_utils.get_version(app_source / 'config.h', 'QBREAK_VERSION_SUFFIX')
|
||||
version_minor = build_utils.get_version(app_source / 'config.h', 'QBREAK_VERSION_MINOR')
|
||||
version_major = build_utils.get_version(app_source / 'config.h', 'QBREAK_VERSION_MAJOR')
|
||||
|
||||
app_version = f'{version_major}.{version_minor}.{version_suffix}'
|
||||
print (f'Found QBreak version: {app_version}')
|
||||
|
||||
# Go to build directory
|
||||
os.chdir(build_dir)
|
||||
|
||||
if platform.system() == 'Linux':
|
||||
print('Linux detected')
|
||||
print('Configure...')
|
||||
retcode = os.system('qmake ../../app')
|
||||
if retcode != 0:
|
||||
print(f'qmake call failed with code {retcode}')
|
||||
exit(retcode)
|
||||
|
||||
print('Build...')
|
||||
retcode = os.system('make -j4')
|
||||
if retcode != 0:
|
||||
print(f'make call failed with code {retcode}')
|
||||
exit(retcode)
|
||||
|
||||
# Build appimage
|
||||
print('Assembling app...')
|
||||
os.chdir('..')
|
||||
|
||||
# Remove possible old image
|
||||
if os.path.exists('appimage_dir'):
|
||||
shutil.rmtree('appimage_dir')
|
||||
|
||||
# Expand image template
|
||||
retcode = os.system('tar -xvzf appimage_dir.tar.gz')
|
||||
if retcode != 0:
|
||||
print(f'Failed to expand template directory, code {retcode}')
|
||||
exit(retcode)
|
||||
|
||||
# Copy binary file
|
||||
shutil.copy('build/qbreak', 'appimage_dir/usr/bin')
|
||||
|
||||
deploy_options = [
|
||||
'-always-overwrite',
|
||||
'-verbose=2',
|
||||
'-appimage',
|
||||
'-qmake=' + os.environ['QTDIR'] + '/bin/qmake',
|
||||
'-unsupported-allow-new-glibc',
|
||||
#'-no-translations',
|
||||
'-extra-plugins=iconengines,platformthemes/libqgtk3.so'
|
||||
]
|
||||
|
||||
desktop_path = 'appimage_dir/usr/share/applications/qbreak.desktop'
|
||||
cmd_deploy = f'./linuxdeployqt {desktop_path} {" ".join(deploy_options)}'
|
||||
retcode = os.system(cmd_deploy)
|
||||
if retcode != 0:
|
||||
print(f'linuxdeployqt failed with code {retcode}')
|
||||
print(cmd_deploy)
|
||||
exit(retcode)
|
||||
|
||||
releases_dir = Path('releases')
|
||||
if not releases_dir.exists():
|
||||
os.mkdir(releases_dir)
|
||||
for f in os.listdir():
|
||||
if f.endswith('x86_64.AppImage') and f.startswith('QBreak'):
|
||||
shutil.move(f, releases_dir / f'qbreak-{app_version}-x86_64.AppImage')
|
||||
|
||||
exit(0)
|
||||
170
scripts/build_utils.py
Normal file
@ -0,0 +1,170 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import multiprocessing
|
||||
import shutil
|
||||
import platform
|
||||
import re
|
||||
import glob
|
||||
import typing
|
||||
import codecs
|
||||
|
||||
# from PyInquirer import prompt, print_json
|
||||
|
||||
|
||||
AR_TEMP_DIR = "ar_temp_dir"
|
||||
|
||||
def show_menu(message, lst):
|
||||
print()
|
||||
# Print selection menu
|
||||
for l in range(len(lst)):
|
||||
print(f'{l}) {lst[l]}')
|
||||
|
||||
return lst[int(input(message))]
|
||||
|
||||
|
||||
def ask_password(message):
|
||||
return input(message)
|
||||
|
||||
|
||||
# Replace substring in a file
|
||||
def inplace_change(filename, old_string, new_string):
|
||||
# Safely read the input filename using 'with'
|
||||
with open(filename) as f:
|
||||
s = f.read()
|
||||
if old_string not in s:
|
||||
print('"{old_string}" not found in {filename}.'.format(**locals()))
|
||||
return
|
||||
|
||||
# Safely write the changed content, if found in the file
|
||||
with open(filename, 'w') as f:
|
||||
print('Changing "{old_string}" to "{new_string}" in {filename}'.format(**locals()))
|
||||
s = s.replace(old_string, new_string)
|
||||
f.write(s)
|
||||
|
||||
|
||||
# Architecture used for VS2019 architecture parameters
|
||||
def make_build(path: str, profile: str, params: dict, merge_libs :bool, generator: str=None, architecture: str = None):
|
||||
cmdline = "cmake "
|
||||
if generator is not None:
|
||||
cmdline += f'-G "{generator}"'
|
||||
if architecture is not None:
|
||||
cmdline += f' -A {architecture}'
|
||||
|
||||
for key in params:
|
||||
cmdline += f" -D {key}={params[key]}"
|
||||
|
||||
cmdline += f" -D CMAKE_BUILD_TYPE={profile}"
|
||||
cmdline += f" {path}"
|
||||
print(f"Cmake configure call: {cmdline}")
|
||||
|
||||
retcode = os.system(cmdline)
|
||||
|
||||
if retcode != 0:
|
||||
return retcode
|
||||
|
||||
if platform.system() == "Windows":
|
||||
cmdline = "cmake --build . --config %s -j%d" % (profile, multiprocessing.cpu_count())
|
||||
else:
|
||||
cmdline = f"cmake --build . -j{multiprocessing.cpu_count()}"
|
||||
|
||||
retcode = os.system(cmdline)
|
||||
if retcode != 0:
|
||||
return retcode
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def get_version(path, name):
|
||||
with codecs.open(path, mode='r', encoding='utf-8') as f:
|
||||
t = f.read()
|
||||
|
||||
pattern = r"#define " + name + r" (?P<number>[\d]+)"
|
||||
m = re.search(pattern=pattern, string=t, flags=re.MULTILINE)
|
||||
if m is not None:
|
||||
return m.group("number")
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def make_chdir(path):
|
||||
# Prepare build directory
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
os.mkdir(path)
|
||||
os.chdir(path)
|
||||
|
||||
|
||||
def upload_to_builds(path):
|
||||
# Build command line
|
||||
if platform.system().lower() == "windows":
|
||||
cmdline = f"pscp.exe {path} uploader@f.sevana.biz:/var/www/f.sevana.biz/public_html/"
|
||||
else:
|
||||
cmdline = f"scp -o \"StrictHostKeyChecking no\" {path} uploader@f.sevana.biz:/var/www/f.sevana.biz/public_html/"
|
||||
|
||||
# Upload
|
||||
retcode = os.system(cmdline)
|
||||
|
||||
# Print results
|
||||
if retcode == 0:
|
||||
print (f"https://f.sevana.biz/{os.path.basename(path)}")
|
||||
else:
|
||||
print ("Failed to upload. Return code: %d" % retcode)
|
||||
|
||||
|
||||
def merge_static_libs(main_lib, build_dir, use_android_ndk: bool):
|
||||
# Find all static libraries
|
||||
print ("Looking for additional static libraries to merge...")
|
||||
files = glob.glob("**/*.a", recursive=True)
|
||||
print (files)
|
||||
|
||||
if platform.system() in ["Linux", "Darwin"]:
|
||||
print ("Merging with binutils...")
|
||||
|
||||
if use_android_ndk:
|
||||
if platform.system() == "Darwin":
|
||||
toolchain_prefix = "darwin-x86_64"
|
||||
else:
|
||||
toolchain_prefix = "linux-x86_64"
|
||||
|
||||
ar_command = f"{path_android_ndk}/toolchains/llvm/prebuilt/{toolchain_prefix}/bin/aarch64-linux-android-ar"
|
||||
else:
|
||||
ar_command = "ar"
|
||||
|
||||
# Extract all object files
|
||||
os.system("mkdir %s" % AR_TEMP_DIR)
|
||||
os.chdir(AR_TEMP_DIR)
|
||||
for libname in files:
|
||||
cmdline_extract = f"{ar_command} -x {os.path.join('..', libname)}"
|
||||
retcode = os.system(cmdline_extract)
|
||||
if retcode != 0:
|
||||
print(f"Failed: {cmdline_extract}")
|
||||
sys.exit(1)
|
||||
|
||||
# Extract object files from main library
|
||||
cmdline_extract = f"{ar_command} -x {os.path.join('..', main_lib)}"
|
||||
retcode = os.system(cmdline_extract)
|
||||
if retcode != 0:
|
||||
print(f"Failed: {cmdline_extract}")
|
||||
sys.exit(1)
|
||||
|
||||
cmdline = f"{ar_command} -qc {os.path.basename(main_lib)} *.o"
|
||||
retcode = os.system(cmdline)
|
||||
if retcode != 0:
|
||||
print(f"Failed: {cmdline}")
|
||||
sys.exit(1)
|
||||
|
||||
# Replace original library with a new one
|
||||
os.chdir("..")
|
||||
os.remove(main_lib)
|
||||
shutil.move(os.path.join(AR_TEMP_DIR, os.path.basename(main_lib)), main_lib)
|
||||
# os.system("strip %s" % main_lib)
|
||||
shutil.rmtree(AR_TEMP_DIR)
|
||||
|
||||
if 'ANDROID_NDK_HOME' in os.environ:
|
||||
path_android_ndk = os.environ['ANDROID_NDK_HOME']
|
||||
else:
|
||||
path_android_ndk = None
|
||||
|
||||
# "~/Library/Android/sdk/ndk/21.0.6113669"
|
||||