/*
* Copyright (c) 2012-2015 Christian Surlykke, Petr Vanek
*
* This file is part of qt-lightdm-greeter
* It is distributed under the LGPL 2.1 or later license.
* Please refer to the LICENSE file for a copy of the license.
*/
#include <QRect>
#include <QApplication>
#include <QDesktopWidget>
#include <QPalette>
#include <QString>
#include <QtDebug>
#include <QTextEdit>
#include <QStyleFactory>
#include <QLabel>
#include <QSvgWidget>
#include <QSvgRenderer>
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QPainter>
#include <sys/types.h>
#include <sys/socket.h>
#include "mainwindow.h"
#include "loginform.h"
#include "settings.h"
#include "global.h"
#include "snake.h"
MainWindow::MainWindow(bool primary, int screen, const QRect &screenRect, QWidget *parent) :
QWidget(parent),
m_ScreenRect(screenRect),
m_LoginForm(nullptr),
m_messages(nullptr),
m_Clock(nullptr),
m_Snake(nullptr),
m_Banner(nullptr),
m_News(nullptr)
{
setObjectName(QString("MainWindow_%1").arg(screen));
// TODO: Check if testMode == false and greeter == NULL, if so display big error message
// instead of exiting/crashing
// display login dialog only in the main screen
if (primary) {
m_LoginForm = new LoginForm(this);
// This hack ensures that the primary screen will have focus
// if there are more screens (move the mouse cursor in the center
// of primary screen - not in the center of all X area). It
// won't affect single-screen environments.
int centerX = screenRect.width()/2 + screenRect.x();
int centerY = screenRect.height()/2 + screenRect.y();
QCursor::setPos(centerX, centerY);
connect(m_LoginForm, &LoginForm::resized, this, &MainWindow::reLayout);
}
// Banner
if (!Settings::bannerImageFile().isEmpty()) {
qWarning() << "Have banner " << Settings::bannerImageFile();
m_Banner = new QSvgWidget(Settings::bannerImageFile(), this);
}
createLogWindow();
createClock();
createNewsWindow();
setGeometry(screenRect);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
if (m_Snake != nullptr) {
delete m_Snake;
m_Snake = nullptr;
}
QWidget::resizeEvent(event);
m_ScreenRect = QRect(this->pos(), event->size());
setBackground();
reLayout();
}
void MainWindow::reLayout()
{
/*
* Everything is layed out manually here, since I don't know how to represent the size constraints
* and interactions of everything using layout classes. You're welcome to improve this, but I double
* dare you to not break any combination of having/not having certain logos or elements displayed.
*/
int newsY = 100;
int newsX = m_ScreenRect.width() / 2;
int newsBottom = m_ScreenRect.height();
int spaceY = m_ScreenRect.height() / 2;
if (m_LoginForm != nullptr) {
spaceY -= m_LoginForm->height() / 2;
int maxX = m_ScreenRect.width() - m_LoginForm->width();
int maxY = m_ScreenRect.height() - m_LoginForm->height();
int defaultX = 50*maxX/100;
int defaultY = 50*maxY/100;
int offsetX = getOffset(Settings::offsetX(), maxX, defaultX);
int offsetY = getOffset(Settings::offsetY(), maxY, defaultY);
m_LoginForm->move(offsetX, offsetY);
m_LoginForm->show();
newsX = m_LoginForm->geometry().right() + 5;
}
// Banner
if (m_Banner != nullptr) {
qWarning() << m_Banner->sizeHint();
if (m_Banner->renderer()->isValid()) {
QSize sh;
int offs = 0;
do {
offs += 20;
sh = m_Banner->sizeHint().scaled(m_ScreenRect.width() - offs, spaceY - offs - 50, Qt::KeepAspectRatio);
} while (offs < 200 && sh.width() > m_ScreenRect.width() / 2 && sh.height() > spaceY / 2);
int yoff = (spaceY - sh.height());
if (yoff < 100) {
yoff = 100;
}
m_Banner->setGeometry((m_ScreenRect.width() - sh.width()) / 2, yoff / 2, sh.width(), sh.height());
newsY = m_Banner->geometry().bottom() + 10;
}
}
for (QWidget *w : m_DecoItems) {
w->setParent(nullptr);
w->deleteLater();
}
// Distro and custom icons left/right bottom
m_DecoItems.clear();
int ls = (spaceY > 500 ? 500 : spaceY);
if (ls > m_ScreenRect.height() / 5) ls = m_ScreenRect.height() / 5;
if (ls > m_ScreenRect.width() / 5) ls = m_ScreenRect.width() / 5;
QRect logoRect(QPoint(0, m_ScreenRect.height() / 3), QSize(ls, m_ScreenRect.height() * 2 / 3));
QSize logoSize = createLogo(logoRect);
QRect distroRect(QPoint(m_ScreenRect.width() - ls, m_ScreenRect.height() - ls), QSize(ls, ls));
QSize distroSize = createDistro(distroRect);
if (distroSize.height() > 0) {
newsBottom -= distroSize.height() - 10;
}
// Log window
if (m_messages != nullptr) {
QRect lwSize(QPoint(logoSize.width(), m_ScreenRect.height() * 3/4), QPoint(m_ScreenRect.width() - distroSize.width(), m_ScreenRect.height()));
lwSize.adjust(10, 10, -10, -10);
m_messages->setGeometry(lwSize);
int ps = lwSize.height() / 20;
if (ps > 20) ps = 20;
m_messages->setFontPointSize(ps);
newsBottom = lwSize.top();
}
if (m_Clock != nullptr) {
m_Clock->setFixedWidth(m_ScreenRect.width());
m_Clock->parentWidget()->setFixedWidth(m_ScreenRect.width());
}
// News widget
if (m_News != nullptr) {
QRect newsSize(QPoint(newsX, newsY), QPoint(m_ScreenRect.width() - 10, newsBottom - 10));
if (newsSize.width() < 200 || newsSize.height() < 80) {
m_News->hide();
} else {
m_News->setGeometry(newsSize);
m_News->show();
}
}
}
void MainWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (m_Snake != nullptr) {
m_Snake->paint(event);
}
}
void MainWindow::mouseDoubleClickEvent(QMouseEvent *)
{
static int clicks = 0;
if (m_Snake == nullptr) {
if (clicks++ > 0) {
m_Snake = new GameCore(this);
}
} else {
m_Snake->addSnake();
}
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Escape:
if (m_Snake != nullptr) {
delete m_Snake;
m_Snake = nullptr;
}
break;
case Qt::Key_Pause:
if (m_Snake != nullptr) {
m_Snake->pauseAndResume();
}
break;
}
}
QSize MainWindow::createLogo(QRect max)
{
QSize retval(0, 0);
QString path = Settings::bottomLeftLogoFile();
if (!path.isEmpty()) { // Single explicit file first
createNextLogo(max, retval, path);
}
path = Settings::bottomLeftLogoDir();
// Check whether it's a file or directory
if (!path.isEmpty() && QFileInfo(path).isDir()) {
QFileInfoList fileInfoList = QDir(path).entryInfoList(QStringList() << "*.svg", QDir::Files, QDir::Name | QDir::Reversed);
for (QFileInfo fileInfo : fileInfoList) {
QString filePath = fileInfo.absoluteFilePath();
createNextLogo(max, retval, filePath);
}
return retval;
}
createNextLogo(max, retval, path);
return retval;
}
void MainWindow::createNextLogo(QRect &max, QSize &retval, const QString &path)
{
if (max.height() <= 0)
return;
QSvgWidget *img = new QSvgWidget(path, this);
if (!img->renderer()->isValid()) {
img->deleteLater();
return;
}
QSize virtualSize = img->sizeHint().scaled(max.width(), max.height(), Qt::KeepAspectRatio);
QSize realSize = img->sizeHint().scaled(max.width() - 20, max.height() - 20, Qt::KeepAspectRatio);
if (virtualSize.width() == 0 || virtualSize.height() == 0) {
img->deleteLater();
return;
}
QRect c(max.left() + 10, max.bottom() - realSize.height() - 10, realSize.width(), realSize.height());
img->setGeometry(c);
m_DecoItems.append(img);
max.setHeight(max.height() - virtualSize.height());
retval.setWidth(qMax(retval.width(), virtualSize.width()));
retval.setHeight(retval.height() + virtualSize.height());
}
QSize MainWindow::createDistro(const QRect &max)
{
QImage img(QString("/etc/distro.png"));
if (img.isNull())
return QSize(0, 0);
QSize realSize = img.size();
QSize virtualSize = realSize.scaled(realSize.width() + 20, realSize.height() + 20, Qt::IgnoreAspectRatio);
if (virtualSize.width() > max.width() || virtualSize.height() > max.height()) {
// This requires that the given rect is square
img = img.scaled(max.width() - 20, max.height() - 20, Qt::KeepAspectRatio, Qt::SmoothTransformation);
realSize = img.size();
virtualSize = realSize.scaled(realSize.width() + 20, realSize.height() + 20, Qt::IgnoreAspectRatio);
}
QPixmap pixmap(QPixmap::fromImage(img));
QLabel *lbl = new QLabel(this);
m_DecoItems.append(lbl);
lbl->setPixmap(pixmap);
QRect c(max.right() - realSize.width() - 10, max.bottom() - realSize.height() - 10, realSize.width(), realSize.height());
lbl->setGeometry(c);
return virtualSize;
}
bool MainWindow::createLogWindow()
{
QString path = Settings::logMessageFile();
if (path.isEmpty())
return false;
QFile f(path);
if (f.size() == 0 || !f.open(QFile::ReadOnly))
return false;
m_messages = new QTextEdit(this);
QTextStream stream(&f);
const QColor black(Qt::black);
while (!stream.atEnd()) {
bool ok = false;
QString line(stream.readLine());
int i = line.indexOf(' ');
if (i > 0) {
QString scol(line.left(i));
uint col = scol.toUInt(&ok, 16);
if (ok) {
m_messages->setTextColor(QColor(QRgb(col)));
line = line.mid(i + 1);
}
}
if (!ok) {
m_messages->setTextColor(black);
}
m_messages->append(line);
}
m_messages->setReadOnly(true);
m_messages->setStyleSheet("border:none; background:rgba(255,255,255,.33); border-radius:5px");
return true;
}
void MainWindow::createNewsWindow()
{
QString path = Settings::newsHtmlFile();
if (path.isEmpty())
return;
QFile f(path);
if (f.size() == 0 || !f.open(QFile::ReadOnly))
return;
m_News = new QTextEdit(this);
m_News->setReadOnly(true);
m_News->setStyleSheet("border:none; background:rgba(255,255,255,.33); border-radius:5px");
m_News->setHtml(QString::fromUtf8(f.readAll()));
}
void MainWindow::createClock()
{
QString style = Settings::clockStyle();
QString backgroundStyle = Settings::clockBackgroundStyle();
if (style == "none")
return;
if (style.isEmpty()) {
style = "border:none; color:#fff; font-size:14pt; qproperty-alignment:AlignRight";
}
if (backgroundStyle.isEmpty()) {
backgroundStyle = "#bg { border:none; background:#888687 }";
} else {
backgroundStyle = "#bg { " + backgroundStyle + " }";
}
QStringList slist = Settings::clockShadow();
int x = 1, y = 1, blur = 1;
QString color = "#555";
if (slist.length() > 0) {
x = slist.at(0).toInt();
}
if (slist.length() > 1) {
y = slist.at(1).toInt();
}
if (slist.length() > 2) {
color = slist.at(2);
}
if (slist.length() > 3) {
blur = slist.at(3).toInt();
}
QWidget *bg = new QWidget(this);
bg->setObjectName("bg");
bg->setFixedWidth(this->width());
bg->setMaximumHeight(500);
bg->setStyleSheet(backgroundStyle);
m_Clock = new QLabel("Today 00:00", bg);
m_Clock->setStyleSheet(style);
QGraphicsDropShadowEffect *pLabelTextShadowEffect = new QGraphicsDropShadowEffect(m_Clock);
pLabelTextShadowEffect->setColor(QColor(color));
pLabelTextShadowEffect->setBlurRadius(blur);
pLabelTextShadowEffect->setOffset(x, y);
m_Clock->setGraphicsEffect(pLabelTextShadowEffect);
m_Clock->setFixedWidth(this->width());
updateClock();
}
bool MainWindow::showLoginForm()
{
return m_LoginForm != nullptr;
}
void MainWindow::setFocus(Qt::FocusReason reason)
{
if (m_LoginForm) {
m_LoginForm->setFocus(reason);
}
else {
QWidget::setFocus(reason);
}
}
int MainWindow::getOffset(QString settingsOffset, int maxVal, int defaultVal)
{
int offset = defaultVal > maxVal ? maxVal : defaultVal;
if (! settingsOffset.isEmpty()) {
if (QRegExp("^\\d+px$", Qt::CaseInsensitive).exactMatch(settingsOffset)) {
offset = settingsOffset.left(settingsOffset.size() - 2).toInt();
if (offset > maxVal) offset = maxVal;
}
else if (QRegExp("^\\d+%$", Qt::CaseInsensitive).exactMatch(settingsOffset)) {
int offsetPct = settingsOffset.left(settingsOffset.size() -1).toInt();
if (offsetPct > 100) offsetPct = 100;
offset = (maxVal * offsetPct)/100;
}
else {
qWarning() << "Could not understand" << settingsOffset
<< "- must be of form <positivenumber>px or <positivenumber>%, e.g. 35px or 25%" ;
}
}
return offset;
}
void MainWindow::setBackground()
{
m_background = QImage();
Qt::AspectRatioMode arMode = Qt::KeepAspectRatioByExpanding;
QString bgPath = Settings::backgrundImageFile();
if (bgPath.length() != 0) {
QSvgRenderer svg(bgPath);
if (svg.isValid()) {
m_background = QImage(m_ScreenRect.size(), QImage::Format_RGB32);
m_background.fill(QColor(225,225,220));
QSize size = svg.defaultSize().scaled(m_background.size(), Qt::KeepAspectRatioByExpanding);
QPainter p(&m_background);
QPoint origin((m_background.width() - size.width()) / 2, (m_background.height() - size.height()) / 2);
svg.render(&p, QRect(origin, size));
} else {
m_background = QImage(bgPath);
if (m_background.isNull()) {
qWarning() << "Not able to read" << bgPath << "as image";
}
}
}
if (m_background.isNull()) {
m_background = Global::getConfigGradient();
arMode = Qt::IgnoreAspectRatio;
}
if (m_background.isNull()) {
// Hard-coded default: Gradient
m_background = QImage(2, 2, QImage::Format_RGB32);
m_background.setPixel(0, 0, 0xffffffff);
m_background.setPixel(1, 0, 0xffffffff);
m_background.setPixel(0, 1, 0xff888687);
m_background.setPixel(1, 1, 0xfff9a72b);
}
QPalette palette;
if (m_background.isNull()) {
palette.setColor(QPalette::Background, Qt::black);
}
else {
m_background = m_background.scaled(m_ScreenRect.width(), m_ScreenRect.height(), arMode, Qt::SmoothTransformation);
int xoff = (m_background.width() - m_ScreenRect.width()) / 2;
int yoff = (m_background.height() - m_ScreenRect.height()) / 2;
if (xoff != 0 || yoff != 0) {
m_background = m_background.copy(xoff, yoff, m_background.width(), m_background.height());
}
QBrush brush(m_background);
palette.setBrush(this->backgroundRole(), brush);
}
this->setPalette(palette);
}
void MainWindow::updateClock()
{
int next = drawClock();
if (next > 0) {
QTimer::singleShot(next, this, SLOT(updateClock()));
}
}
int MainWindow::drawClock()
{
if (m_Clock == nullptr)
return 0;
QDateTime time = QDateTime::currentDateTime();
QLocale loc;
m_Clock->setText(time.toString(loc.dateFormat() + " HH:mm "));
m_Clock->adjustSize();
reinterpret_cast<QWidget*>(m_Clock->parent())->adjustSize();
return (60 - time.time().second()) * 1000 + 100;
}