2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-09-05 08:55:59 +00:00

initial commit for 0.4.18 version of Telegram Desktop

This commit is contained in:
John Preston
2014-05-30 12:53:19 +04:00
parent 6d9ac2c475
commit 4221fe666f
1933 changed files with 137552 additions and 3 deletions

View File

@@ -0,0 +1,96 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "animation.h"
#include <QtCore/QTimer>
namespace {
AnimationManager *manager = 0;
};
namespace anim {
float64 linear(const float64 &delta, const float64 &dt) {
return delta * dt;
}
float64 sineInOut(const float64 &delta, const float64 &dt) {
return -(delta / 2) * (cos(M_PI * dt) - 1);
}
float64 halfSine(const float64 &delta, const float64 &dt) {
return delta * sin(M_PI * dt / 2);
}
float64 easeOutBack(const float64 &delta, const float64 &dt) {
static const float64 s = 1.70158;
const float64 t = dt - 1;
return delta * (t * t * ((s + 1) * t + s) + 1);
}
float64 easeInCirc(const float64 &delta, const float64 &dt) {
return -delta * (sqrt(1 - dt * dt) - 1);
}
float64 easeOutCirc(const float64 &delta, const float64 &dt) {
const float64 t = dt - 1;
return delta * sqrt(1 - t * t);
}
float64 easeInCubic(const float64 &delta, const float64 &dt) {
return delta * dt * dt * dt;
}
float64 easeOutCubic(const float64 &delta, const float64 &dt) {
const float64 t = dt - 1;
return delta * (t * t * t + 1);
}
float64 easeInQuint(const float64 &delta, const float64 &dt) {
const float64 t2 = dt * dt;
return delta * t2 * t2 * dt;
}
float64 easeOutQuint(const float64 &delta, const float64 &dt) {
const float64 t = dt - 1, t2 = t * t;
return delta * (t2 * t2 * t + 1);
}
void start(Animated *obj) {
if (!manager) return;
manager->start(obj);
}
void stop(Animated *obj) {
if (!manager) return;
manager->stop(obj);
}
void startManager() {
delete manager;
manager = new AnimationManager();
}
void stopManager() {
delete manager;
manager = 0;
}
}

View File

@@ -0,0 +1,292 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "types.h"
#include <QtCore/QTimer>
#include <QtGui/QColor>
class Animated;
namespace anim {
typedef float64 (*transition)(const float64 &delta, const float64 &dt);
float64 linear(const float64 &delta, const float64 &dt);
float64 sineInOut(const float64 &delta, const float64 &dt);
float64 halfSine(const float64 &delta, const float64 &dt);
float64 easeOutBack(const float64 &delta, const float64 &dt);
float64 easeInCirc(const float64 &delta, const float64 &dt);
float64 easeOutCirc(const float64 &delta, const float64 &dt);
float64 easeInCubic(const float64 &delta, const float64 &dt);
float64 easeOutCubic(const float64 &delta, const float64 &dt);
float64 easeInQuint(const float64 &delta, const float64 &dt);
float64 easeOutQuint(const float64 &delta, const float64 &dt);
class fvalue { // float animated value
public:
fvalue() {
}
fvalue(const float64 &from) : _cur(from), _from(from), _delta(0) {
}
fvalue(const float64 &from, const float64 &to) : _cur(from), _from(from), _delta(to - from) {
}
void start(const float64 &to) {
_from = _cur;
_delta = to - _from;
}
void restart() {
_delta = _from + _delta - _cur;
_from = _cur;
}
const float64 &current() const {
return _cur;
}
void update(const float64 &dt, transition func) {
_cur = _from + (*func)(_delta, dt);
}
void finish() {
_cur = _from + _delta;
_from = _cur;
_delta = 0;
}
private:
float64 _cur, _from, _delta;
};
class ivalue { // int animated value
public:
ivalue() {
}
ivalue(int32 from) : _cur(from), _from(float64(from)), _delta(0) {
}
ivalue(int32 from, int32 to) : _cur(from), _from(float64(from)), _delta(float64(to - from)) {
}
void start(int32 to) {
_from = float64(_cur);
_delta = float64(to) - _from;
}
void restart() {
_delta = _from + _delta - float64(_cur);
_from = float64(_cur);
}
int32 current() const {
return _cur;
}
void update(const float64 &dt, transition func) {
_cur = qRound(_from + (*func)(_delta, dt));
}
void finish() {
_cur = qRound(_from + _delta);
_from = _cur;
_delta = 0;
}
private:
int32 _cur;
float64 _from, _delta;
};
class cvalue { // QColor animated value
public:
cvalue() {
}
cvalue(const QColor &from) : _cur(from), _from_r(from.redF()), _from_g(from.greenF()), _from_b(from.blueF()), _from_a(from.alphaF()), _delta_r(0), _delta_g(0), _delta_b(0), _delta_a(0) {
}
cvalue(const QColor &from, const QColor &to)
: _cur(from)
, _from_r(from.redF()), _from_g(from.greenF()), _from_b(from.blueF()), _from_a(from.alphaF())
, _delta_r(to.redF() - from.redF()), _delta_g(to.greenF() - from.greenF()), _delta_b(to.blueF() - from.blueF()), _delta_a(to.alphaF() - from.alphaF())
{
}
void start(const QColor &to) {
_from_r = _cur.redF();
_from_g = _cur.greenF();
_from_b = _cur.blueF();
_from_a = _cur.alphaF();
_delta_r = to.redF() - _from_r;
_delta_g = to.greenF() - _from_g;
_delta_b = to.blueF() - _from_b;
_delta_a = to.alphaF() - _from_a;
}
void restart() {
_delta_r = _from_r + _delta_r - _cur.redF();
_delta_g = _from_g + _delta_g - _cur.greenF();
_delta_b = _from_b + _delta_b - _cur.blueF();
_delta_a = _from_a + _delta_a - _cur.alphaF();
_from_r = _cur.redF();
_from_g = _cur.greenF();
_from_b = _cur.blueF();
_from_a = _cur.alphaF();
}
const QColor &current() const {
return _cur;
}
void update(const float64 &dt, transition func) {
_cur.setRedF(_from_r + (*func)(_delta_r, dt));
_cur.setGreenF(_from_g + (*func)(_delta_g, dt));
_cur.setBlueF(_from_b + (*func)(_delta_b, dt));
_cur.setAlphaF(_from_a + (*func)(_delta_a, dt));
}
void finish() {
_cur.setRedF(_from_r + _delta_r);
_cur.setGreenF(_from_g + _delta_g);
_cur.setBlueF(_from_b + _delta_b);
_cur.setAlphaF(_from_a + _delta_a);
_from_r = _cur.redF();
_from_g = _cur.greenF();
_from_b = _cur.blueF();
_from_a = _cur.alphaF();
_delta_r = _delta_g = _delta_b = _delta_a = 0;
}
private:
QColor _cur;
float64 _from_r, _from_g, _from_b, _from_a, _delta_r, _delta_g, _delta_b, _delta_a;
};
void start(Animated *obj);
void stop(Animated *obj);
void startManager();
void stopManager();
};
class Animated {
public:
Animated() : animInProcess(false), animStarted(0) {
}
virtual bool animStep(float64 ms) = 0;
void animReset() {
animStarted = float64(getms());
}
virtual ~Animated() {
if (animating()) {
anim::stop(this);
}
}
bool animating() const {
return animInProcess;
}
private:
float64 animStarted;
bool animInProcess;
friend class AnimationManager;
};
class AnimationManager : public QObject {
Q_OBJECT
public:
AnimationManager() : timer(this), iterating(false) {
timer.setSingleShot(false);
connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
}
void start(Animated *obj) {
obj->animReset();
if (iterating) {
toStart.insert(obj);
if (!toStop.isEmpty()) {
toStop.remove(obj);
}
} else {
if (!objs.size()) {
timer.start(7);
}
objs.insert(obj);
}
obj->animInProcess = true;
}
void stop(Animated *obj) {
if (iterating) {
toStop.insert(obj);
if (!toStart.isEmpty()) {
toStart.insert(obj);
}
} else {
AnimObjs::iterator i = objs.find(obj);
if (i != objs.cend()) {
objs.erase(i);
if (!objs.size()) {
timer.stop();
}
}
}
obj->animInProcess = false;
}
public slots:
void timeout() {
iterating = true;
float64 ms = float64(getms());
for (AnimObjs::iterator i = objs.begin(), e = objs.end(); i != e; ) {
Animated *obj = *i;
if (!obj->animStep(ms - obj->animStarted)) {
i = objs.erase(i);
obj->animInProcess = false;
} else {
++i;
}
}
iterating = false;
if (!toStart.isEmpty()) {
for (AnimObjs::iterator i = toStart.begin(), e = toStart.end(); i != e; ++i) {
objs.insert(*i);
}
toStart.clear();
}
if (!toStop.isEmpty()) {
for (AnimObjs::iterator i = toStop.begin(), e = toStop.end(); i != e; ++i) {
objs.remove(*i);
}
toStop.clear();
}
if (!objs.size()) {
timer.stop();
}
}
private:
typedef QSet<Animated*> AnimObjs;
AnimObjs objs;
AnimObjs toStart;
AnimObjs toStop;
QTimer timer;
bool iterating;
};

View File

@@ -0,0 +1,74 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "boxshadow.h"
BoxShadow::BoxShadow(const style::rect &topLeft) : _size(topLeft.width()) {
QImage cornersImage(_size * 2, _size * 2, QImage::Format_ARGB32_Premultiplied);
{
QPainter p(&cornersImage);
p.drawPixmap(QPoint(0, 0), App::sprite(), topLeft);
}
uchar *bits = cornersImage.bits();
if (bits) {
for (
quint32 *p = (quint32*)bits, *end = (quint32*)(bits + cornersImage.byteCount());
p < end;
++p
) {
*p = (*p ^ 0x00ffffff) << 24;
}
}
{
QPainter p(&cornersImage);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(0, _size, cornersImage.mirrored(), 0, _size, _size, _size);
}
{
QPainter p(&cornersImage);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(_size, 0, cornersImage.mirrored(true, false), _size, 0, _size, _size * 2);
}
_corners = QPixmap::fromImage(cornersImage);
_colors.reserve(_size);
uchar prev = 0;
for (int32 i = 0; i < _size; ++i) {
uchar a = (cornersImage.pixel(QPoint(i, _size - 1)) >> 24);
if (a < prev) break;
_colors.push_back(style::color(0, 0, 0, a));
prev = a;
}
}
void BoxShadow::paint(QPainter &p, const QRect &box, const QPoint &shift, int32 flags) {
int32 count = _colors.size(), minus = _size - count + 1;
bool left = (flags & Left), top = (flags & Top), right = (flags & Right), bottom = (flags & Bottom);
if (left && top) p.drawPixmap(box.left() - _size + minus + shift.x(), box.top() - _size + minus + shift.y(), _corners, 0, 0, _size, _size);
if (right && top) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.top() - _size + minus + shift.y(), _corners, _size, 0, _size, _size);
if (right && bottom) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, _size, _size, _size, _size);
if (left && bottom) p.drawPixmap(box.left() - _size + minus + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, 0, _size, _size, _size);
for (int32 i = 1; i <= count; ++i) {
p.setPen(_colors[i - 1]->p);
if (top) p.drawLine(box.left() + (left ? minus : 0) + shift.x(), box.top() - count + i + shift.y(), box.right() - (right ? minus : 0) + shift.x(), box.top() - count + i + shift.y());
if (right) p.drawLine(box.right() + count - i + shift.x(), box.top() + (top ? minus : 0) + shift.y(), box.right() + count - i + shift.x(), box.bottom() - (bottom ? minus : 0) + shift.y());
if (bottom) p.drawLine(box.right() - (right ? minus : 0) + shift.x(), box.bottom() + count - i + shift.y(), box.left() + (left ? minus : 0) + shift.x(), box.bottom() + count - i + shift.y());
if (left) p.drawLine(box.left() - count + i + shift.x(), box.bottom() - (bottom ? minus : 0) + shift.y(), box.left() - count + i + shift.x(), box.top() + (top ? minus : 0) + shift.y());
}
}

View File

@@ -0,0 +1,40 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
class BoxShadow {
public:
enum {
Left = 1,
Top = 2,
Right = 4,
Bottom = 8
};
BoxShadow(const style::rect &topLeft);
void paint(QPainter &p, const QRect &box, const QPoint &shift = QPoint(0, 1), int32 flags = Left | Top | Right | Bottom);
private:
int32 _size;
QPixmap _corners;
QVector<style::color> _colors;
};

View File

@@ -0,0 +1,113 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "button.h"
Button::Button(QWidget *parent) : TWidget(parent), _state(StateNone), _acceptBoth(false) {
}
void Button::leaveEvent(QEvent *e) {
if (_state & StateDown) return;
if (_state & StateOver) {
int oldState = _state;
_state &= ~StateOver;
emit stateChanged(oldState, ButtonByHover);
}
setMouseTracking(false);
return TWidget::leaveEvent(e);
}
void Button::enterEvent(QEvent *e) {
if (!(_state & StateOver)) {
int oldState = _state;
_state |= StateOver;
emit stateChanged(oldState, ButtonByHover);
}
setMouseTracking(true);
return TWidget::enterEvent(e);
}
void Button::setAcceptBoth(bool acceptBoth) {
_acceptBoth = acceptBoth;
}
void Button::mousePressEvent(QMouseEvent *e) {
if (_acceptBoth || e->buttons() & Qt::LeftButton) {
if (!(_state & StateOver)) {
enterEvent(0);
}
if (!(_state & StateDown)) {
int oldState = _state;
_state |= StateDown;
emit stateChanged(oldState, ButtonByPress);
e->accept();
}
}
}
void Button::mouseMoveEvent(QMouseEvent *e) {
if (rect().contains(e->pos())) {
if (!(_state & StateOver)) {
int oldState = _state;
_state |= StateOver;
emit stateChanged(oldState, ButtonByHover);
}
} else {
if (_state & StateOver) {
int oldState = _state;
_state &= ~StateOver;
emit stateChanged(oldState, ButtonByHover);
}
}
}
void Button::mouseReleaseEvent(QMouseEvent *e) {
if (_state & StateDown) {
int oldState = _state;
_state &= ~StateDown;
emit stateChanged(oldState, ButtonByPress);
if (oldState & StateOver) {
emit clicked();
} else {
leaveEvent(e);
}
}
}
void Button::setDisabled(bool disabled) {
int oldState = _state;
if (disabled && !(_state & StateDisabled)) {
_state |= StateDisabled;
emit stateChanged(oldState, ButtonByUser);
} else if (!disabled && (_state & StateDisabled)) {
_state &= ~StateDisabled;
emit stateChanged(oldState, ButtonByUser);
}
}
void Button::clearState() {
int oldState = _state;
_state = StateNone;
emit stateChanged(oldState, ButtonByUser);
}
int Button::getState() const {
return _state;
}

View File

@@ -0,0 +1,68 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtWidgets/QWidget>
#include "gui/twidget.h"
typedef enum {
ButtonByUser = 0x00, // by clearState() call
ButtonByPress = 0x01,
ButtonByHover = 0x02,
} ButtonStateChangeSource;
class Button : public TWidget {
Q_OBJECT
public:
Button(QWidget *parent);
enum {
StateNone = 0x00,
StateOver = 0x01,
StateDown = 0x02,
StateDisabled = 0x04,
};
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
void clearState();
int getState() const;
void setDisabled(bool disabled = true);
bool disabled() const {
return (_state & StateDisabled);
}
void setAcceptBoth(bool acceptBoth = true);
signals:
void clicked();
void stateChanged(int oldState, ButtonStateChangeSource source);
protected:
int _state;
bool _acceptBoth;
};

View File

@@ -0,0 +1,83 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "gui/countrycodeinput.h"
#include "gui/countryinput.h"
CountryCodeInput::CountryCodeInput(QWidget *parent, const style::flatInput &st) : FlatInput(parent, st), _nosignal(false) {
}
void CountryCodeInput::startErasing(QKeyEvent *e) {
setFocus();
keyPressEvent(e);
}
void CountryCodeInput::codeSelected(const QString &code) {
QString old(text());
setText('+' + code);
_nosignal = true;
correctValue(0, old);
_nosignal = false;
emit changed();
}
void CountryCodeInput::correctValue(QKeyEvent *e, const QString &was) {
QString oldText(text()), newText, addToNumber;
int oldPos(cursorPosition()), newPos(-1), oldLen(oldText.length()), start = 0, digits = 5;
newText.reserve(oldLen + 1);
newText += '+';
if (oldLen && oldText[0] == '+') {
++start;
}
for (int i = start; i < oldLen; ++i) {
QChar ch(oldText[i]);
if (ch.isDigit()) {
if (!digits || !--digits) {
addToNumber += ch;
} else {
newText += ch;
}
}
if (i == oldPos) {
newPos = newText.length();
}
}
if (!addToNumber.isEmpty()) {
QString validCode = findValidCode(newText.mid(1));
addToNumber = newText.mid(1 + validCode.length()) + addToNumber;
newText = '+' + validCode;
}
if (newPos < 0 || newPos > newText.length()) {
newPos = newText.length();
}
if (newText != oldText) {
setText(newText);
if (newPos != oldPos) {
setCursorPosition(newPos);
}
}
if (!_nosignal && was != newText) {
emit codeChanged(newText.mid(1));
}
if (!addToNumber.isEmpty()) {
emit addedToNumber(addToNumber);
}
}

View File

@@ -0,0 +1,47 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/flatinput.h"
class CountryCodeInput : public FlatInput {
Q_OBJECT
public:
CountryCodeInput(QWidget *parent, const style::flatInput &st);
public slots:
void startErasing(QKeyEvent *e);
void codeSelected(const QString &code);
signals:
void codeChanged(const QString &code);
void addedToNumber(const QString &added);
protected:
void correctValue(QKeyEvent *e, const QString &was);
private:
bool _nosignal;
};

View File

@@ -0,0 +1,608 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "lang.h"
#include "application.h"
#include "gui/countryinput.h"
#include "gui/scrollarea.h"
namespace {
struct CountryInfo {
CountryInfo(const char *_name, const char *_iso2, const char *_code) : name(_name), iso2(_iso2), code(_code) {
}
const char *name, *iso2, *code;
};
#include "countries.h"
typedef QHash<QString, const CountryInfo *> CountriesByCode;
typedef QHash<QString, const CountryInfo *> CountriesByISO2;
typedef QList<const CountryInfo *> CountriesFiltered;
typedef QVector<int> CountriesIds;
typedef QHash<QChar, CountriesIds> CountriesByLetter;
typedef QVector<QString> CountryNames;
typedef QVector<CountryNames> CountriesNames;
CountriesByCode countriesByCode;
CountriesByISO2 countriesByISO2;
CountriesFiltered countriesFiltered, countriesAll, *countriesNow = &countriesAll;
CountriesByLetter countriesByLetter;
CountriesNames countriesNames;
QString lastFilter, lastValidISO;
int countriesCount = sizeof(countries) / sizeof(countries[0]);
void initCountries() {
if (countriesByCode.size()) return;
countriesByCode.reserve(countriesCount);
countriesByISO2.reserve(countriesCount);
for (int i = 0; i < countriesCount; ++i) {
const CountryInfo *info(countries + i);
countriesByCode.insert(info->code, info);
CountriesByISO2::const_iterator already = countriesByISO2.constFind(info->iso2);
if (already != countriesByISO2.cend()) {
QString badISO = info->iso2;
badISO;
}
countriesByISO2.insert(info->iso2, info);
}
countriesAll.reserve(countriesCount);
countriesFiltered.reserve(countriesCount);
countriesNames.resize(countriesCount);
}
}
QString findValidCode(QString fullCode) {
while (fullCode.length()) {
CountriesByCode::const_iterator i = countriesByCode.constFind(fullCode);
if (i != countriesByCode.cend()) {
return (*i)->code;
}
fullCode = fullCode.mid(0, fullCode.length() - 1);
}
return "";
}
CountryInput::CountryInput(QWidget *parent, const style::countryInput &st) : QWidget(parent), _st(st), _active(false), _select(0), _text(lang(lng_country_code)) {
initCountries();
resize(_st.width, _st.height + _st.ptrSize.height());
QImage trImage(_st.ptrSize.width(), _st.ptrSize.height(), QImage::Format_ARGB32_Premultiplied);
{
static const QPoint trPoints[3] = {
QPoint(0, 0),
QPoint(_st.ptrSize.width(), 0),
QPoint(qCeil(trImage.width() / 2.), trImage.height())
};
QPainter p(&trImage);
p.setRenderHint(QPainter::Antialiasing);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, trImage.width(), trImage.height(), st::transparent->b);
p.setPen(Qt::NoPen);
p.setBrush(_st.bgColor->b);
p.drawPolygon(trPoints, 3);
}
_arrow = QPixmap::fromImage(trImage);
_inner = QRect(0, 0, _st.width, _st.height);
_arrowRect = QRect((st::inpIntroCountryCode.width - _arrow.width() - 1) / 2, _st.height, _arrow.width(), _arrow.height());
}
void CountryInput::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(_inner, _st.bgColor->b);
p.drawPixmap(_arrowRect.x(), _arrowRect.top(), _arrow);
p.setFont(_st.font->f);
p.drawText(rect().marginsRemoved(_st.textMrg), _text, QTextOption(_st.align));
}
void CountryInput::mouseMoveEvent(QMouseEvent *e) {
bool newActive = _inner.contains(e->pos()) || _arrowRect.contains(e->pos());
if (_active != newActive) {
_active = newActive;
setCursor(_active ? style::cur_pointer : style::cur_default);
}
}
void CountryInput::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_active) {
Window *w = App::wnd();
if (w->focusWidget()) w->focusWidget()->clearFocus();
if (_select) {
_select->hide();
_select->deleteLater();
}
_select = new CountrySelect();
connect(_select, SIGNAL(countryChosen(const QString &)), this, SLOT(onChooseCountry(const QString &)));
connect(_select, SIGNAL(countryFinished()), this, SLOT(onFinishCountry()));
}
}
void CountryInput::enterEvent(QEvent *e) {
setMouseTracking(true);
}
void CountryInput::leaveEvent(QEvent *e) {
setMouseTracking(false);
_active = false;
setCursor(style::cur_default);
}
void CountryInput::onChooseCode(const QString &code) {
if (_select) {
_select->hide();
_select->deleteLater();
_select = 0;
emit selectClosed();
}
if (code.length()) {
CountriesByCode::const_iterator i = countriesByCode.constFind(code);
if (i != countriesByCode.cend()) {
const CountryInfo *info = *i;
lastValidISO = info->iso2;
setText(QString::fromUtf8(info->name));
} else {
setText(lang(lng_bad_country_code));
}
} else {
setText(lang(lng_country_code));
}
update();
}
bool CountryInput::onChooseCountry(const QString &iso) {
CountriesByISO2::const_iterator i = countriesByISO2.constFind(iso);
const CountryInfo *info = (i == countriesByISO2.cend()) ? 0 : (*i);
if (info) {
lastValidISO = info->iso2;
setText(QString::fromUtf8(info->name));
emit codeChanged(info->code);
update();
return true;
}
return false;
}
void CountryInput::onFinishCountry() {
if (_select) {
_select->hide();
_select->deleteLater();
_select = 0;
emit selectClosed();
}
}
void CountryInput::setText(const QString &newText) {
_text = _st.font->m.elidedText(newText, Qt::ElideRight, width() - _st.textMrg.left() - _st.textMrg.right());
}
CountryInput::~CountryInput() {
delete _select;
}
CountryList::CountryList(QWidget *parent, const style::countryList &st) : QWidget(parent), _sel(0), _mouseSel(false),
_st(st) {
CountriesByISO2::const_iterator l = countriesByISO2.constFind(lastValidISO);
bool seenLastValid = false;
int already = countriesAll.size();
countriesByLetter.clear();
const CountryInfo *lastValid = (l == countriesByISO2.cend()) ? 0 : (*l);
for (int i = 0; i < countriesCount; ++i) {
const CountryInfo *ins = lastValid ? (i ? (countries + i - (seenLastValid ? 0 : 1)) : lastValid) : (countries + i);
if (lastValid && i && ins == lastValid) {
seenLastValid = true;
++ins;
}
if (already > i) {
countriesAll[i] = ins;
} else {
countriesAll.push_back(ins);
}
QStringList namesList = QString::fromUtf8(ins->name).toLower().split(QRegularExpression("[\\s\\-]"), QString::SkipEmptyParts);
CountryNames &names(countriesNames[i]);
int l = namesList.size();
names.resize(0);
names.reserve(l);
for (int j = 0, l = namesList.size(); j < l; ++j) {
QString name = namesList[j].trimmed();
if (!name.length()) continue;
QChar ch = name[0];
CountriesIds &v(countriesByLetter[ch]);
if (v.isEmpty() || v.back() != i) {
v.push_back(i);
}
names.push_back(name);
}
}
lastFilter = "";
resetList();
}
void CountryList::resetList() {
countriesNow = &countriesAll;
if (lastFilter.length()) {
QChar first = lastFilter[0].toLower();
CountriesIds &ids(countriesByLetter[first]);
QStringList filterList = lastFilter.split(QRegularExpression("[\\s\\-]"), QString::SkipEmptyParts);
int l = filterList.size();
CountryNames filter;
filter.reserve(l);
for (int i = 0; i < l; ++i) {
QString filterName = filterList[i].trimmed();
if (!filterName.length()) continue;
filter.push_back(filterName);
}
CountryNames::const_iterator fb = filter.cbegin(), fe = filter.cend(), fi;
countriesFiltered.clear();
for (CountriesIds::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
int index = *i;
CountryNames &names(countriesNames[index]);
CountryNames::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
for (fi = fb; fi != fe; ++fi) {
QString filterName(*fi);
for (ni = nb; ni != ne; ++ni) {
if ((*ni).indexOf(*fi) == 0) {
break;
}
}
if (ni == ne) {
break;
}
}
if (fi == fe) {
countriesFiltered.push_back(countriesAll[index]);
}
}
countriesNow = &countriesFiltered;
}
resize(width(), countriesNow->length() ? (countriesNow->length() * _st.rowHeight + 2 * _st.verticalMargin) : parentWidget()->height());
setSelected(0);
}
void CountryList::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
bool trivial = (rect() == r);
QPainter p(this);
if (!trivial) {
p.setClipRect(r);
}
int l = countriesNow->size();
if (l) {
int from = (r.top() > _st.verticalMargin) ? (r.top() - _st.verticalMargin) / _st.rowHeight : 0, to = from + r.height() / _st.rowHeight + 1;
if (to >= l) {
if (from >= l) return;
to = l;
}
p.setFont(_st.font->f);
QRectF textRect(_st.margin + _st.borderMargin, _st.verticalMargin + from * _st.rowHeight, width() - 2 * _st.margin - 2 * _st.borderMargin, _st.rowHeight - _st.borderWidth);
for (int i = from; i < to; ++i) {
bool sel = (i == _sel);
if (sel) {
p.fillRect(_st.borderMargin, _st.verticalMargin + i * _st.rowHeight, width() - 2 * _st.borderMargin, _st.rowHeight, _st.bgHovered->b);
}
p.setFont(_st.font->f);
p.setPen(_st.color->p);
p.drawText(textRect, _st.font->m.elidedText(QString::fromUtf8((*countriesNow)[i]->name), Qt::ElideRight, width() - 2 * _st.margin - _st.codeWidth), QTextOption(style::al_left));
p.setFont(_st.codeFont->f);
p.setPen(_st.codeColor->p);
p.drawText(textRect, QString("+") + (*countriesNow)[i]->code, QTextOption(style::al_right));
textRect.setBottom(textRect.bottom() + _st.rowHeight);
textRect.setTop(textRect.top() + _st.rowHeight);
}
} else {
p.setFont(_st.notFoundFont->f);
p.setPen(_st.notFoundColor->p);
p.drawText(r, lang(lng_country_none), QTextOption(style::al_center));
}
}
void CountryList::mouseMoveEvent(QMouseEvent *e) {
_mouseSel = true;
_mousePos = mapToGlobal(e->pos());
onUpdateSelected(true);
}
void CountryList::onUpdateSelected(bool force) {
QPoint p(mapFromGlobal(_mousePos));
if (!force && !rect().contains(p) || !_mouseSel) return;
int newSelected = p.y();
newSelected = (newSelected > _st.verticalMargin) ? (newSelected - _st.verticalMargin) / _st.rowHeight : 0;
int l = countriesNow->size();
if (newSelected >= l) newSelected = l - 1;
if (newSelected < 0) newSelected = 0;
if (newSelected != _sel) {
_sel = newSelected;
update();
}
}
void CountryList::mousePressEvent(QMouseEvent *e) {
_mouseSel = true;
_mousePos = mapToGlobal(e->pos());
onUpdateSelected(true);
emit countrySelected();
}
void CountryList::enterEvent(QEvent *e) {
setMouseTracking(true);
}
void CountryList::leaveEvent(QEvent *e) {
setMouseTracking(false);
}
void CountryList::updateFiltered() {
resetList();
}
void CountryList::onParentGeometryChanged() {
_mousePos = QCursor::pos();
if (rect().contains(mapFromGlobal(_mousePos))) {
setMouseTracking(true);
onUpdateSelected(true);
}
}
void CountryList::selectSkip(int delta) {
setSelected(_sel + delta);
}
void CountryList::selectSkipPage(int h, int delta) {
setSelected(_sel + delta * (h / int(_st.rowHeight) - 1));
}
void CountryList::setSelected(int newSelected) {
_mouseSel = false;
if (newSelected >= countriesNow->size()) {
newSelected = countriesNow->size() - 1;
}
if (newSelected < 0) {
newSelected = 0;
}
_sel = newSelected;
emit mustScrollTo(_sel * _st.rowHeight, (_sel + 1) * _st.rowHeight);
update();
}
QString CountryList::getSelectedCountry() const {
if (lastFilter.length()) {
if (_sel < countriesFiltered.size()) {
return countriesFiltered[_sel]->iso2;
} else {
return "";
}
}
return countriesAll[_sel]->iso2;
}
CountrySelect::CountrySelect() : QWidget(App::wnd()),
_scroll(this, st::scrollCountries), _list(&_scroll),
_filter(this, st::inpCountry, lang(lng_country_ph)),
_doneButton(this, lang(lng_country_done), st::btnSelectDone),
_cancelButton(this, lang(lng_cancel), st::btnSelectCancel),
_innerLeft(0), _innerTop(0), _innerWidth(0), _innerHeight(0), _result("none"),
a_alpha(0), a_bgAlpha(0), a_coord(st::countriesSlideShift), _shadow(st::boxShadow) {
setGeometry(App::wnd()->rect());
App::wnd()->topWidget(this);
connect(App::wnd(), SIGNAL(resized(const QSize &)), this, SLOT(onParentResize(const QSize &)));
connect(&_doneButton, SIGNAL(clicked()), this, SLOT(onCountryChoose()));
connect(&_cancelButton, SIGNAL(clicked()), this, SLOT(onCountryCancel()));
connect(&_scroll, SIGNAL(scrollFinished()), this, SLOT(onScrollFinished()));
connect(&_scroll, SIGNAL(geometryChanged()), &_list, SLOT(onParentGeometryChanged()));
connect(&_scroll, SIGNAL(scrolled()), &_list, SLOT(onUpdateSelected()));
connect(&_list, SIGNAL(countrySelected()), this, SLOT(onCountryChoose()));
connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
connect(&_list, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int)));
show();
setFocus();
_scroll.setWidget(&_list);
_scroll.setFocusPolicy(Qt::NoFocus);
prepareAnimation(0);
}
void CountrySelect::prepareAnimation(int to) {
if (to) {
if (_result == "none") _result = "";
a_alpha.start(0);
af_alpha = st::countriesAlphaHideFunc;
a_bgAlpha.start(0);
af_bgAlpha = st::countriesBackHideFunc;
a_coord.start(to * st::countriesSlideShift);
af_coord = st::countriesHideFunc;
} else {
_result = "none";
a_alpha.start(1);
af_alpha = st::countriesAlphaShowFunc;
a_bgAlpha.start(1);
af_bgAlpha = st::countriesBackShowFunc;
a_coord.start(0);
af_coord = st::countriesShowFunc;
}
_cache = grab(QRect(_innerLeft, _innerTop, _innerWidth, _innerHeight));
_scroll.hide();
_doneButton.hide();
_cancelButton.hide();
_filter.hide();
anim::start(this);
}
void CountrySelect::paintEvent(QPaintEvent *e) {
bool trivial = (rect() == e->rect());
QPainter p(this);
if (!trivial) {
p.setClipRect(e->rect());
}
p.setOpacity(st::layerAlpha * a_bgAlpha.current());
p.fillRect(rect(), st::layerBG->b);
if (animating()) {
p.setOpacity(a_alpha.current());
p.drawPixmap(a_coord.current() + _innerLeft, _innerTop, _cache);
} else {
p.setOpacity(1);
QRect inner(_innerLeft, _innerTop, _innerWidth, _innerHeight);
_shadow.paint(p, inner);
if (trivial || e->rect().intersects(inner)) {
// fill bg
p.fillRect(inner, st::white->b);
// paint shadows
p.fillRect(_innerLeft, _innerTop + st::participantFilter.height, _innerWidth, st::scrollDef.topsh, st::scrollDef.shColor->b);
// paint button sep
p.setPen(st::btnSelectSep->p);
p.drawLine(_innerLeft + st::btnSelectCancel.width, _innerTop + _innerHeight - st::btnSelectCancel.height, _innerLeft + st::btnSelectCancel.width, _innerTop + _innerHeight - 1);
// draw box title / text
p.setPen(st::black->p);
p.setFont(st::addContactTitleFont->f);
p.drawText(_innerLeft + st::addContactTitlePos.x(), _innerTop + st::addContactTitlePos.y() + st::addContactTitleFont->ascent, lang(lng_country_select));
}
}
}
void CountrySelect::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
onCountryCancel();
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
onCountryChoose();
} else if (e->key() == Qt::Key_Down) {
_list.selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_list.selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_list.selectSkipPage(_scroll.height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_list.selectSkipPage(_scroll.height(), -1);
}
}
void CountrySelect::onFilterUpdate() {
QString newFilter(_filter.text().trimmed().toLower());
if (newFilter != lastFilter) {
lastFilter = newFilter;
_list.updateFiltered();
}
}
void CountrySelect::resizeEvent(QResizeEvent *e) {
if (width() != e->oldSize().width()) {
_innerWidth = st::newGroupNamePadding.left() + _filter.width() + st::newGroupNamePadding.right();
_innerLeft = (width() - _innerWidth) / 2;
_list.resize(_innerWidth, _list.height());
}
if (height() != e->oldSize().height()) {
_innerTop = st::introSelectDelta;
_innerHeight = height() - _innerTop - st::introSelectDelta;
if (_innerHeight > st::introSelectMaxHeight) {
_innerHeight = st::introSelectMaxHeight;
_innerTop = (height() - _innerHeight) / 2;
}
}
_filter.move(_innerLeft + st::newGroupNamePadding.left(), _innerTop + st::contactsAdd.height + st::newGroupNamePadding.top());
int32 scrollTop = _filter.y() + _filter.height() + st::newGroupNamePadding.bottom();
int32 scrollHeight = _innerHeight - st::contactsAdd.height - st::newGroupNamePadding.top() - _filter.height() - st::newGroupNamePadding.bottom() - _cancelButton.height();
_scroll.setGeometry(_innerLeft, scrollTop, _innerWidth, scrollHeight);
int btnTop = scrollTop + scrollHeight;
_cancelButton.move(_innerLeft, btnTop);
_doneButton.move(_innerLeft + _innerWidth - _doneButton.width(), btnTop);
}
bool CountrySelect::animStep(float64 ms) {
float64 dt = ms / st::countriesSlideDuration;
bool res = true;
if (dt >= 1) {
a_alpha.finish();
a_bgAlpha.finish();
a_coord.finish();
_cache = QPixmap();
_scroll.show();
_doneButton.show();
_cancelButton.show();
_filter.show();
_filter.setFocus();
if (_result != "none") {
QTimer::singleShot(0, this, SIGNAL(countryFinished()));
}
res = false;
} else {
a_alpha.update(dt, af_alpha);
a_bgAlpha.update(dt, af_bgAlpha);
a_coord.update(dt, af_coord);
}
update();
return res;
}
void CountrySelect::onParentResize(const QSize &newSize) {
resize(App::wnd()->size());
}
void CountrySelect::onCountryCancel() {
finish("");
}
void CountrySelect::onCountryChoose() {
finish(_list.getSelectedCountry());
}
void CountrySelect::finish(const QString &res) {
_result = res;
prepareAnimation(_result.length() ? -1 : 1);
emit countryChosen(_result);
}
void CountrySelect::onScrollFinished() {
_filter.setFocus();
}
CountrySelect::~CountrySelect() {
if (App::wnd()) {
App::wnd()->noTopWidget(this);
}
}

View File

@@ -0,0 +1,162 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtWidgets/QWidget>
#include "style.h"
#include "gui/flatinput.h"
#include "gui/scrollarea.h"
#include "gui/flatbutton.h"
#include "gui/boxshadow.h"
QString findValidCode(QString fullCode);
class CountrySelect;
class CountryInput : public QWidget {
Q_OBJECT
public:
CountryInput(QWidget *parent, const style::countryInput &st);
void paintEvent(QPaintEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mousePressEvent(QMouseEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
~CountryInput();
public slots:
void onChooseCode(const QString &code);
bool onChooseCountry(const QString &country);
void onFinishCountry();
signals:
void codeChanged(const QString &code);
void selectClosed();
private:
void setText(const QString &newText);
QPixmap _arrow;
QRect _inner, _arrowRect;
style::countryInput _st;
bool _active;
QString _text;
CountrySelect *_select;
};
class CountryList : public QWidget {
Q_OBJECT
public:
CountryList(QWidget *parent, const style::countryList &st = st::countryList);
void paintEvent(QPaintEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mousePressEvent(QMouseEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
void selectSkip(int delta);
void selectSkipPage(int h, int delta);
void updateFiltered();
QString getSelectedCountry() const;
public slots:
void onUpdateSelected(bool force = false);
void onParentGeometryChanged();
signals:
void countrySelected();
void mustScrollTo(int scrollToTop, int scrollToBottom);
private:
void resetList();
void setSelected(int newSelected);
int _sel;
style::countryList _st;
QPoint _mousePos;
bool _mouseSel;
};
class CountrySelect : public QWidget, public Animated {
Q_OBJECT
public:
CountrySelect();
void paintEvent(QPaintEvent *e);
void keyPressEvent(QKeyEvent *e);
void resizeEvent(QResizeEvent *e);
bool animStep(float64 ms);
~CountrySelect();
signals:
void countryChosen(const QString &country = QString());
void countryFinished();
public slots:
void onParentResize(const QSize &newSize);
void onCountryChoose();
void onCountryCancel();
void onScrollFinished();
void onFilterUpdate();
private:
void finish(const QString &res);
void prepareAnimation(int to);
QString _result;
FlatInput _filter;
ScrollArea _scroll;
CountryList _list;
FlatButton _doneButton, _cancelButton;
int32 _innerLeft, _innerTop, _innerWidth, _innerHeight;
anim::fvalue a_alpha, a_bgAlpha;
anim::ivalue a_coord;
anim::transition af_alpha, af_bgAlpha, af_coord;
QPixmap _cache;
BoxShadow _shadow;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
void initEmoji();
EmojiPtr getEmoji(uint32 code);
void findEmoji(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode);
inline bool emojiEdge(const QChar *ch) {
return true;
switch (ch->unicode()) {
case '.': case ',': case ':': case ';': case '!': case '?': case '#': case '@':
case '(': case ')': case '[': case ']': case '{': case '}': case '<': case '>':
case '+': case '=': case '-': case '_': case '*': case '/': case '\\': case '^': case '$':
case '"': case '\'':
case 8212: case 171: case 187: // --, <<, >>
return true;
}
return false;
}
inline QString replaceEmojis(const QString &text) {
QString result;
const QChar *emojiEnd = text.unicode(), *e = text.cend();
bool canFindEmoji = true, consumePrevious = false;
for (const QChar *ch = emojiEnd; ch != e;) {
uint32 emojiCode = 0;
const QChar *newEmojiEnd = 0;
if (canFindEmoji) {
findEmoji(ch, e, newEmojiEnd, emojiCode);
}
if (emojiCode) {
// if (newEmojiEnd < e && newEmojiEnd->unicode() == ' ') ++newEmojiEnd;
if (result.isEmpty()) result.reserve(text.size());
if (ch > emojiEnd + (consumePrevious ? 1 : 0)) {
result.append(emojiEnd, ch - emojiEnd - (consumePrevious ? 1 : 0));
}
if (emojiCode > 65535) {
result.append(QChar((emojiCode >> 16) & 0xFFFF));
}
result.append(QChar(emojiCode & 0xFFFF));
ch = emojiEnd = newEmojiEnd;
canFindEmoji = true;
consumePrevious = false;
} else {
if (false && (ch->unicode() == QChar::Space || ch->unicode() == QChar::Nbsp)) {
canFindEmoji = true;
consumePrevious = true;
} else if (emojiEdge(ch)) {
canFindEmoji = true;
consumePrevious = false;
} else {
canFindEmoji = false;
}
++ch;
}
}
if (result.isEmpty()) return text;
if (emojiEnd < e) result.append(emojiEnd, e - emojiEnd);
return result;
}
EmojiPack emojiPack(DBIEmojiTab tab);

View File

@@ -0,0 +1,168 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "gui/filedialog.h"
#include "app.h"
#include "application.h"
void filedialogInit() {
// hack to restore previous dir without hurting performance
if (cDialogLastPath().isEmpty()) {
QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
settings.beginGroup(QLatin1String("Qt"));
QByteArray sd = settings.value(QLatin1String("filedialog")).toByteArray();
QDataStream stream(&sd, QIODevice::ReadOnly);
if (!stream.atEnd()) {
int version = 3, _QFileDialogMagic = 190;
QByteArray splitterState;
QByteArray headerData;
QList<QUrl> bookmarks;
QStringList history;
QString currentDirectory;
qint32 marker;
qint32 v;
qint32 viewMode;
stream >> marker;
stream >> v;
if (marker == _QFileDialogMagic && v == version) {
stream >> splitterState
>> bookmarks
>> history
>> currentDirectory
>> headerData
>> viewMode;
cSetDialogLastPath(currentDirectory);
}
}
if (cDialogHelperPath().isEmpty()) {
QDir temppath(cWorkingDir() + "tdata/tdummy/");
if (!temppath.exists()) {
temppath.mkpath(temppath.absolutePath());
}
if (temppath.exists()) {
cSetDialogHelperPath(temppath.absolutePath());
}
}
}
}
// multipleFiles: 1 - multi open, 0 - single open, -1 - single save
bool _filedialogGetFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, int multipleFiles, const QString &startFile = QString()) {
filedialogInit();
// hack for fast non-native dialog create
QFileDialog dialog(App::wnd(), caption, cDialogHelperPathFinal(), filter);
dialog.setModal(true);
if (multipleFiles >= 0) { // open file or files
dialog.setFileMode(multipleFiles ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
} else if (multipleFiles < -1) { // save dir
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setFileMode(QFileDialog::Directory);
dialog.setOption(QFileDialog::ShowDirsOnly);
} else { // save file
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
}
dialog.show();
if (!cDialogLastPath().isEmpty()) dialog.setDirectory(cDialogLastPath());
if (multipleFiles == -1) {
QString toSelect(startFile);
#ifdef Q_OS_WIN
int32 lastSlash = toSelect.lastIndexOf('/');
if (lastSlash >= 0) {
toSelect = toSelect.mid(lastSlash + 1);
}
int32 lastBackSlash = toSelect.lastIndexOf('\\');
if (lastBackSlash >= 0) {
toSelect = toSelect.mid(lastBackSlash + 1);
}
#endif
dialog.selectFile(toSelect);
}
int res = dialog.exec();
cSetDialogLastPath(dialog.directory().absolutePath());
if (res == QDialog::Accepted) {
if (multipleFiles > 0) {
files = dialog.selectedFiles();
} else {
files = dialog.selectedFiles().mid(0, 1);
}
if (multipleFiles >= 0) {
#ifdef Q_OS_WIN
remoteContent = dialog.selectedRemoteContent();
#endif
}
return true;
}
files = QStringList();
remoteContent = QByteArray();
return false;
}
bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter) {
return _filedialogGetFiles(files, remoteContent, caption, filter, 1);
}
bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter) {
QStringList files;
bool result = _filedialogGetFiles(files, remoteContent, caption, filter, 0);
file = files.isEmpty() ? QString() : files.at(0);
return result;
}
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName) {
QStringList files;
QByteArray remoteContent;
bool result = _filedialogGetFiles(files, remoteContent, caption, filter, -1, startName);
file = files.isEmpty() ? QString() : files.at(0);
return result;
}
bool filedialogGetDir(QString &dir, const QString &caption) {
QStringList files;
QByteArray remoteContent;
bool result = _filedialogGetFiles(files, remoteContent, caption, QString(), -2);
dir = files.isEmpty() ? QString() : files.at(0);
return result;
}
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path) {
filedialogInit();
time_t t = time(NULL);
struct tm tm;
mylocaltime(&tm, &t);
QChar zero('0');
QDir dir(path.isEmpty() ? cDialogLastPath() : path);
QString base = prefix + QString("_%1-%2-%3_%4-%5-%6").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, zero).arg(tm.tm_mday, 2, 10, zero).arg(tm.tm_hour, 2, 10, zero).arg(tm.tm_min, 2, 10, zero).arg(tm.tm_sec, 2, 10, zero);
QString nameBase = dir.absolutePath() + '/' + base, name = nameBase + extension;
for (int i = 0; QFileInfo(name).exists(); ++i) {
name = nameBase + QString(" (%1)").arg(i + 2) + extension;
}
return name;
}

View File

@@ -0,0 +1,26 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
void filedialogInit();
bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter);
bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter);
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName);
bool filedialogGetDir(QString &dir, const QString &caption);
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString());

View File

@@ -0,0 +1,204 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "gui/flatbutton.h"
FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent),
_text(text), _opacity(1),
_st(st),
a_bg(st.bgColor->c), a_text(st.color->c) {
if (_st.width < 0) {
_st.width = _st.font->m.width(text) - _st.width;
} else if (!_st.width) {
_st.width = _st.font->m.width(text) + _st.height - _st.font->height;
}
connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource)));
resize(_st.width, _st.height);
setCursor(_st.cursor);
}
void FlatButton::setOpacity(float64 o) {
_opacity = o;
update();
}
void FlatButton::setText(const QString &text) {
_text = text;
update();
}
void FlatButton::setWidth(int32 w) {
_st.width = w;
resize(_st.width, height());
}
bool FlatButton::animStep(float64 ms) {
float64 dt = ms / _st.duration;
bool res = true;
if (dt >= 1) {
a_bg.finish();
a_text.finish();
res = false;
} else {
a_bg.update(dt, anim::linear);
a_text.update(dt, anim::linear);
}
update();
return res;
}
void FlatButton::onStateChange(int oldState, ButtonStateChangeSource source) {
style::color bgColorTo = (_state & StateOver) ? ((_state & StateDown) ? _st.downBgColor : _st.overBgColor) : _st.bgColor;
style::color colorTo = (_state & StateOver) ? ((_state & StateDown) ? _st.downColor : _st.overColor) : _st.color;
a_bg.start(bgColorTo->c);
a_text.start(colorTo->c);
if (source == ButtonByUser || source == ButtonByPress) {
anim::stop(this);
a_bg.finish();
a_text.finish();
update();
} else {
anim::start(this);
}
}
void FlatButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
QRect r(0, height() - _st.height, width(), _st.height);
p.setOpacity(_opacity);
p.fillRect(r, a_bg.current());
p.setFont(((_state & StateOver) ? _st.overFont : _st.font)->f);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen(a_text.current());
r.setTop((_state & StateOver) ? ((_state & StateDown) ? _st.downTextTop : _st.overTextTop) : _st.textTop);
p.drawText(r, _text, QTextOption(Qt::AlignHCenter));
}
BottomButton::BottomButton(QWidget *w, const QString &t, const style::flatButton &s) : FlatButton(w, t, s) {
resize(width(), height() + 1);
}
void BottomButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setPen(st::scrollDef.shColor->p);
p.drawLine(0, 0, width(), 0);
FlatButton::paintEvent(e);
}
LinkButton::LinkButton(QWidget *parent, const QString &text, const style::linkButton &st) : Button(parent), _text(text), _st(st) {
connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource)));
resize(_st.font->m.width(_text), _st.font->height);
setCursor(style::cur_pointer);
}
void LinkButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setFont(((_state & StateOver) ? _st.overFont : _st.font)->f);
p.setPen(((_state & StateDown) ? _st.downColor : ((_state & StateOver) ? _st.overColor : _st.color))->p);
p.drawText(0, ((_state & StateOver) ? _st.overFont : _st.font)->ascent, _text);
}
void LinkButton::setText(const QString &text) {
_text = text;
resize(_st.font->m.width(_text), _st.font->height);
update();
}
void LinkButton::onStateChange(int oldState, ButtonStateChangeSource source) {
update();
}
LinkButton::~LinkButton() {
}
IconedButton::IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text) : Button(parent), _opacity(1),
_text(text), _st(st), a_opacity(_st.opacity), a_bg(_st.bgColor->c) {
if (_st.width < 0) {
_st.width = _st.font->m.width(text) - _st.width;
} else if (!_st.width) {
_st.width = _st.font->m.width(text) + _st.height - _st.font->height;
}
connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource)));
resize(_st.width, _st.height);
setCursor(_st.cursor);
}
void IconedButton::setOpacity(float64 opacity) {
_opacity = opacity;
update();
}
bool IconedButton::animStep(float64 ms) {
float64 dt = ms / _st.duration;
bool res = true;
if (dt >= 1) {
a_opacity.finish();
a_bg.finish();
res = false;
} else {
a_opacity.update(dt, anim::linear);
a_bg.update(dt, anim::linear);
}
update();
return res;
}
void IconedButton::onStateChange(int oldState, ButtonStateChangeSource source) {
a_opacity.start((_state & (StateOver | StateDown)) ? _st.overOpacity : _st.opacity);
a_bg.start(((_state & (StateOver | StateDown)) ? _st.overBgColor : _st.bgColor)->c);
if (source == ButtonByUser || source == ButtonByPress) {
anim::stop(this);
a_opacity.finish();
a_bg.finish();
update();
} else {
anim::start(this);
}
}
void IconedButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setOpacity(_opacity);
p.fillRect(e->rect(), a_bg.current());
p.setOpacity(a_opacity.current() * _opacity);
if (!_text.isEmpty()) {
p.setFont(_st.font->f);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen(_st.color->p);
const QPoint &t((_state & StateDown) ? _st.downTextPos : _st.textPos);
p.drawText(t.x(), t.y() + _st.font->ascent, _text);
}
const QRect &i((_state & StateDown) ? _st.downIcon : _st.icon);
if (i.width()) {
const QPoint &t((_state & StateDown) ? _st.downIconPos : _st.iconPos);
p.drawPixmap(t, App::sprite(), i);
}
}

View File

@@ -0,0 +1,114 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/button.h"
#include "gui/flatcheckbox.h"
#include "gui/animation.h"
#include "style.h"
class FlatButton : public Button, public Animated {
Q_OBJECT
public:
FlatButton(QWidget *parent, const QString &text, const style::flatButton &st);
bool animStep(float64 ms);
void paintEvent(QPaintEvent *e);
void setOpacity(float64 o);
void setText(const QString &text);
void setWidth(int32 w);
~FlatButton() {
}
public slots:
void onStateChange(int oldState, ButtonStateChangeSource source);
private:
QString _text;
int32 _textWidth;
style::flatButton _st;
anim::cvalue a_bg, a_text;
float64 _opacity;
};
class BottomButton : public FlatButton {
public:
BottomButton(QWidget *parent, const QString &text, const style::flatButton &st);
void paintEvent(QPaintEvent *e);
};
class LinkButton : public Button {
Q_OBJECT
public:
LinkButton(QWidget *parent, const QString &text, const style::linkButton &st = st::btnDefLink);
void paintEvent(QPaintEvent *e);
void setText(const QString &text);
~LinkButton();
public slots:
void onStateChange(int oldState, ButtonStateChangeSource source);
private:
QString _text;
style::linkButton _st;
};
class IconedButton : public Button, public Animated {
Q_OBJECT
public:
IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text = QString());
bool animStep(float64 ms);
void paintEvent(QPaintEvent *e);
void setOpacity(float64 o);
public slots:
void onStateChange(int oldState, ButtonStateChangeSource source);
private:
QString _text;
int32 _textWidth;
style::iconedButton _st;
anim::fvalue a_opacity;
anim::cvalue a_bg;
float64 _opacity;
};

View File

@@ -0,0 +1,211 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "lang.h"
#include "flatcheckbox.h"
FlatCheckbox::FlatCheckbox(QWidget *parent, const QString &text, bool checked, const style::flatCheckbox &st) : Button(parent),
_text(text), _checked(checked), _st(st), _opacity(1), a_over(0, 0) {
connect(this, SIGNAL(clicked()), this, SLOT(onClicked()));
connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource)));
setCursor(_st.cursor);
int32 w = _st.width, h = _st.height;
if (w <= 0) w = _st.textLeft + _st.font->m.width(_text);
if (h <= 0) h = qMax(_st.font->height, _st.imageRect.height());
resize(QSize(w, h));
}
bool FlatCheckbox::checked() const {
return _checked;
}
void FlatCheckbox::setChecked(bool checked) {
if (_checked != checked) {
_checked = checked;
emit changed();
update();
}
}
void FlatCheckbox::setOpacity(float64 o) {
_opacity = o;
update();
}
void FlatCheckbox::onClicked() {
if (_state & StateDisabled) return;
setChecked(!checked());
}
void FlatCheckbox::onStateChange(int oldState, ButtonStateChangeSource source) {
if ((_state & StateOver) && !(oldState & StateOver)) {
a_over.start(1);
anim::start(this);
} else if (!(_state & StateOver) && (oldState & StateOver)) {
a_over.start(0);
anim::start(this);
}
if ((_state & StateDisabled) && !(oldState & StateDisabled)) {
setCursor(_st.disabledCursor);
anim::start(this);
} else if (!(_state & StateDisabled) && (oldState & StateDisabled)) {
setCursor(_st.cursor);
anim::start(this);
}
}
void FlatCheckbox::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setOpacity(_opacity);
if (_st.bgColor != st::transparent) {
p.fillRect(rect(), _st.bgColor->b);
}
p.setFont(_st.font->f);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen((_state & StateDisabled ? _st.disColor : _st.textColor)->p);
QRect tRect(rect());
tRect.setTop(_st.textTop);
tRect.setLeft(_st.textLeft);
p.drawText(tRect, _text, QTextOption(style::al_topleft));
if (_state & StateDisabled) {
QRect sRect(_checked ? _st.chkDisImageRect : _st.disImageRect);
p.drawPixmap(_st.imagePos, App::sprite(), sRect);
} else if (_checked && _st.chkImageRect == _st.chkOverImageRect || !_checked && _st.imageRect == _st.overImageRect) {
p.setOpacity(_opacity);
QRect sRect(_checked ? _st.chkImageRect : _st.imageRect);
p.drawPixmap(_st.imagePos, App::sprite(), sRect);
} else {
if (a_over.current() < 1) {
QRect sRect(_checked ? _st.chkImageRect : _st.imageRect);
p.drawPixmap(_st.imagePos, App::sprite(), sRect);
}
if (a_over.current() > 0) {
p.setOpacity(_opacity * a_over.current());
QRect sRect(_checked ? _st.chkOverImageRect : _st.overImageRect);
p.drawPixmap(_st.imagePos, App::sprite(), sRect);
}
}
}
bool FlatCheckbox::animStep(float64 ms) {
float64 dt = ms / _st.duration;
bool res = true;
if (dt >= 1) {
a_over.finish();
res = false;
} else {
a_over.update(dt, _st.bgFunc);
}
update();
return res;
}
class RadiobuttonsGroup : public QSet<FlatRadiobutton*> {
typedef QSet<FlatRadiobutton*> Parent;
public:
RadiobuttonsGroup(const QString &name) : _name(name), _val(0) {
}
void remove(FlatRadiobutton * const &radio);
int32 val() const {
return _val;
}
void setVal(int32 val) {
_val = val;
}
private:
QString _name;
int32 _val;
};
class Radiobuttons : public QMap<QString, RadiobuttonsGroup*> {
typedef QMap<QString, RadiobuttonsGroup*> Parent;
public:
RadiobuttonsGroup *reg(const QString &group) {
Parent::const_iterator i = constFind(group);
if (i == cend()) {
i = insert(group, new RadiobuttonsGroup(group));
}
return i.value();
}
int remove(const QString &group) {
Parent::iterator i = find(group);
if (i != cend()) {
delete i.value();
erase(i);
return 1;
}
return 0;
}
~Radiobuttons() {
for (Parent::const_iterator i = cbegin(), e = cend(); i != e; ++i) {
delete *i;
}
}
};
namespace {
Radiobuttons radioButtons;
}
void RadiobuttonsGroup::remove(FlatRadiobutton * const &radio) {
Parent::remove(radio);
if (isEmpty()) {
radioButtons.remove(_name);
}
}
FlatRadiobutton::FlatRadiobutton(QWidget *parent, const QString &group, int32 value, const QString &text, bool checked, const style::flatCheckbox &st) :
FlatCheckbox(parent, text, checked, st), _value(value), _group(radioButtons.reg(group)) {
_group->insert(this);
connect(this, SIGNAL(changed()), this, SLOT(onChanged()));
if (this->checked()) onChanged();
}
void FlatRadiobutton::onChanged() {
if (checked()) {
int32 uncheck = _group->val();
if (uncheck != _value) {
_group->setVal(_value);
for (RadiobuttonsGroup::const_iterator i = _group->cbegin(), e = _group->cend(); i != e; ++i) {
if ((*i)->val() == uncheck) {
(*i)->setChecked(false);
}
}
}
} else if (_group->val() == _value) {
setChecked(true);
}
}
FlatRadiobutton::~FlatRadiobutton() {
_group->remove(this);
}

View File

@@ -0,0 +1,80 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/button.h"
class FlatCheckbox : public Button, public Animated {
Q_OBJECT
public:
FlatCheckbox(QWidget *parent, const QString &text, bool checked = false, const style::flatCheckbox &st = st::cbDefFlat);
bool checked() const;
void setChecked(bool checked);
bool animStep(float64 ms);
void paintEvent(QPaintEvent *e);
void setOpacity(float64 o);
public slots:
void onClicked();
void onStateChange(int oldState, ButtonStateChangeSource source);
signals:
void changed();
private:
style::flatCheckbox _st;
anim::fvalue a_over;
QString _text;
style::font _font;
float64 _opacity;
bool _checked;
};
class RadiobuttonsGroup;
class FlatRadiobutton : public FlatCheckbox {
Q_OBJECT
public:
FlatRadiobutton(QWidget *parent, const QString &group, int32 value, const QString &text, bool checked = false, const style::flatCheckbox &st = st::rbDefFlat);
int32 val() const {
return _value;
}
~FlatRadiobutton();
public slots:
void onChanged();
private:
RadiobuttonsGroup *_group;
int32 _value;
};

View File

@@ -0,0 +1,266 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "flatinput.h"
namespace {
class FlatInputStyle : public QCommonStyle {
public:
FlatInputStyle() {
}
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const {
}
QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const {
switch (r) {
case SE_LineEditContents:
const FlatInput *w = widget ? qobject_cast<const FlatInput*>(widget) : 0;
return w ? w->getTextRect() : QCommonStyle::subElementRect(r, opt, widget);
break;
}
return QCommonStyle::subElementRect(r, opt, widget);
}
};
FlatInputStyle _flatInputStyle;
}
FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString &pholder, const QString &v) : QLineEdit(v, parent), _oldtext(v),
_st(st), _phVisible(!v.length()), _kev(0), a_borderColor(st.borderColor->c), a_bgColor(st.bgColor->c), _notingBene(0),
a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c) {
resize(_st.width, _st.height);
setFont(_st.font->f);
setAlignment(_st.align);
_ph = _st.font->m.elidedText(pholder, Qt::ElideRight, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1);
QPalette p(palette());
p.setColor(QPalette::Text, _st.textColor->c);
setPalette(p);
connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChange(const QString &)));
connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(onTextEdited()));
setStyle(&_flatInputStyle);
setTextMargins(0, 0, 0, 0);
setContentsMargins(0, 0, 0, 0);
setAttribute(Qt::WA_AcceptTouchEvents);
_touchTimer.setSingleShot(true);
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
}
void FlatInput::onTouchTimer() {
_touchRightButton = true;
}
bool FlatInput::event(QEvent *e) {
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (ev->device()->type() == QTouchDevice::TouchScreen) {
touchEvent(ev);
return QLineEdit::event(e);
}
}
return QLineEdit::event(e);
}
void FlatInput::touchEvent(QTouchEvent *e) {
switch (e->type()) {
case QEvent::TouchBegin:
if (_touchPress || e->touchPoints().isEmpty()) return;
_touchTimer.start(QApplication::startDragTime());
_touchPress = true;
_touchMove = _touchRightButton = false;
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
break;
case QEvent::TouchUpdate:
if (!_touchPress || e->touchPoints().isEmpty()) return;
if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
_touchMove = true;
}
break;
case QEvent::TouchEnd:
if (!_touchPress) return;
if (!_touchMove && window()) {
Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton);
QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart));
if (_touchRightButton) {
QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart);
contextMenuEvent(&contextEvent);
}
}
_touchTimer.stop();
_touchPress = _touchMove = _touchRightButton = false;
break;
case QEvent::TouchCancel:
_touchPress = false;
_touchTimer.stop();
break;
}
}
QRect FlatInput::getTextRect() const {
return rect().marginsRemoved(_st.textMrg + QMargins(-2, -1, -2, -1));
}
void FlatInput::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), a_bgColor.current());
if (_st.borderWidth) {
p.setPen(a_borderColor.current());
for (uint32 i = 0; i < _st.borderWidth; ++i) {
p.drawRect(i, i, width() - 2 * i - 1, height() - 2 * i - 1);
}
}
if (_st.imgRect.width()) {
p.drawPixmap(_st.imgPos, App::sprite(), _st.imgRect);
}
bool phDraw = _phVisible;
if (animating()) {
p.setOpacity(a_phAlpha.current());
phDraw = true;
}
if (phDraw) {
p.save();
p.setClipRect(rect());
QRect phRect(_st.textMrg.left() + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom());
p.setFont(_st.font->f);
p.setPen(a_phColor.current());
p.drawText(phRect, _ph, QTextOption(_st.phAlign));
p.restore();
}
QLineEdit::paintEvent(e);
}
void FlatInput::focusInEvent(QFocusEvent *e) {
a_phColor.start(_st.phFocusColor->c);
if (_notingBene <= 0) {
a_borderColor.start(_st.borderActive->c);
}
a_bgColor.start(_st.bgActive->c);
anim::start(this);
QLineEdit::focusInEvent(e);
emit focused();
}
void FlatInput::focusOutEvent(QFocusEvent *e) {
a_phColor.start(_st.phColor->c);
if (_notingBene <= 0) {
a_borderColor.start(_st.borderColor->c);
}
a_bgColor.start(_st.bgColor->c);
anim::start(this);
QLineEdit::focusOutEvent(e);
emit blurred();
}
QSize FlatInput::sizeHint() const {
return geometry().size();
}
QSize FlatInput::minimumSizeHint() const {
return geometry().size();
}
bool FlatInput::animStep(float64 ms) {
float dt = ms / _st.phDuration;
bool res = true;
if (dt >= 1) {
res = false;
a_phLeft.finish();
a_phAlpha.finish();
a_phColor.finish();
a_bgColor.finish();
if (_notingBene > 0) {
_notingBene = -1;
a_borderColor.start((hasFocus() ? _st.borderActive : _st.borderColor)->c);
anim::start(this);
return true;
} else if (_notingBene) {
_notingBene = 0;
}
a_borderColor.finish();
} else {
a_phLeft.update(dt, _st.phLeftFunc);
a_phAlpha.update(dt, _st.phAlphaFunc);
a_phColor.update(dt, _st.phColorFunc);
a_bgColor.update(dt, _st.phColorFunc);
a_borderColor.update(dt, _st.phColorFunc);
}
update();
return res;
}
void FlatInput::updatePlaceholder() {
bool vis = !text().length();
if (vis == _phVisible) return;
a_phLeft.start(vis ? 0 : _st.phShift);
a_phAlpha.start(vis ? 1 : 0);
anim::start(this);
_phVisible = vis;
}
void FlatInput::correctValue(QKeyEvent *e, const QString &was) {
}
void FlatInput::keyPressEvent(QKeyEvent *e) {
QString was(text());
_kev = e;
QLineEdit::keyPressEvent(e);
if (was == text()) { // call correct manually
correctValue(_kev, was);
_oldtext = text();
if (was != _oldtext) emit changed();
updatePlaceholder();
}
if (e->key() == Qt::Key_Escape) {
emit cancelled();
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
emit accepted();
}
_kev = 0;
}
void FlatInput::onTextEdited() {
QString was(_oldtext);
correctValue(_kev, was);
_oldtext = text();
if (was != _oldtext) emit changed();
updatePlaceholder();
}
void FlatInput::onTextChange(const QString &text) {
_oldtext = text;
}
void FlatInput::notaBene() {
_notingBene = 1;
setFocus();
a_borderColor.start(_st.borderError->c);
anim::start(this);
}

View File

@@ -0,0 +1,87 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtWidgets/QLineEdit>
#include "style.h"
#include "animation.h"
class FlatInput : public QLineEdit, public Animated {
Q_OBJECT
public:
FlatInput(QWidget *parent, const style::flatInput &st, const QString &ph = QString(), const QString &val = QString());
QString val() const;
bool event(QEvent *e);
void touchEvent(QTouchEvent *e);
void paintEvent(QPaintEvent *e);
void focusInEvent(QFocusEvent *e);
void focusOutEvent(QFocusEvent *e);
void keyPressEvent(QKeyEvent *e);
void notaBene();
void updatePlaceholder();
QRect getTextRect() const;
bool animStep(float64 ms);
QSize sizeHint() const;
QSize minimumSizeHint() const;
public slots:
void onTextChange(const QString &text);
void onTextEdited();
void onTouchTimer();
signals:
void changed();
void cancelled();
void accepted();
void focused();
void blurred();
protected:
virtual void correctValue(QKeyEvent *e, const QString &was);
private:
QString _ph, _oldtext;
QKeyEvent *_kev;
bool _phVisible;
anim::ivalue a_phLeft;
anim::fvalue a_phAlpha;
anim::cvalue a_phColor, a_borderColor, a_bgColor;
int _notingBene;
style::flatInput _st;
style::font _font;
QTimer _touchTimer;
bool _touchPress, _touchRightButton, _touchMove;
QPoint _touchStart;
};

View File

@@ -0,0 +1,122 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "gui/flatlabel.h"
namespace {
TextParseOptions _labelOptions = {
TextParseMultiline, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
}
FlatLabel::FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent),
_st(st), _tst(tst), _text(_st.width ? _st.width : QFIXED_MAX), _opacity(1) {
setRichText(text);
}
void FlatLabel::setText(const QString &text) {
textstyleSet(&_tst);
_text.setText(_st.font, text, _labelOptions);
textstyleRestore();
int32 w = _st.width ? _st.width : _text.maxWidth(), h = _text.countHeight(w);
resize(w, h);
}
void FlatLabel::setRichText(const QString &text) {
textstyleSet(&_tst);
_text.setRichText(_st.font, text, _labelOptions);
textstyleRestore();
int32 w = _st.width ? _st.width : _text.maxWidth(), h = _text.countHeight(w);
resize(w, h);
setMouseTracking(_text.hasLinks());
}
void FlatLabel::setLink(uint16 lnkIndex, const TextLinkPtr &lnk) {
_text.setLink(lnkIndex, lnk);
}
void FlatLabel::mouseMoveEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
}
void FlatLabel::mousePressEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
if (textlnkOver()) {
textlnkDown(textlnkOver());
update();
}
}
void FlatLabel::mouseReleaseEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
if (textlnkOver() && textlnkOver() == textlnkDown()) {
textlnkOver()->onClick(e->button());
}
textlnkDown(TextLinkPtr());
}
void FlatLabel::leaveEvent(QEvent *e) {
if (_myLink) {
if (textlnkOver() == _myLink) {
textlnkOver(TextLinkPtr());
update();
}
_myLink = TextLinkPtr();
setCursor(style::cur_default);
}
}
void FlatLabel::updateLink() {
_lastMousePos = QCursor::pos();
updateHover();
}
void FlatLabel::updateHover() {
QPoint m(mapFromGlobal(_lastMousePos));
bool wasMy = (_myLink == textlnkOver());
textstyleSet(&_tst);
_myLink = _text.link(m.x(), m.y(), width(), _st.align);
textstyleRestore();
if (_myLink != textlnkOver()) {
if (wasMy || _myLink || rect().contains(m)) {
textlnkOver(_myLink);
}
setCursor(_myLink ? style::cur_pointer : style::cur_default);
update();
}
}
void FlatLabel::setOpacity(float64 o) {
_opacity = o;
update();
}
void FlatLabel::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setOpacity(_opacity);
textstyleSet(&_tst);
_text.draw(p, 0, 0, width(), _st.align, e->rect().y(), e->rect().bottom());
textstyleRestore();
}

View File

@@ -0,0 +1,54 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "style.h"
class FlatLabel : public TWidget {
Q_OBJECT
public:
FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle);
void paintEvent(QPaintEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void leaveEvent(QEvent *e);
void updateLink();
void setOpacity(float64 o);
void setText(const QString &text);
void setRichText(const QString &text);
void setLink(uint16 lnkIndex, const TextLinkPtr &lnk);
private:
void updateHover();
Text _text;
style::flatLabel _st;
style::textStyle _tst;
float64 _opacity;
QPoint _lastMousePos;
TextLinkPtr _myLink;
};

View File

@@ -0,0 +1,483 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "flattextarea.h"
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(v, parent), _oldtext(v),
_st(st), _phVisible(!v.length()), _ph(pholder), _fakeMargin(0),
a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c),
_touchPress(false), _touchRightButton(false), _touchMove(false), _replacingEmojis(false) {
setAcceptRichText(false);
resize(_st.width, _st.font->height);
setFont(_st.font->f);
setAlignment(_st.align);
_phelided = _st.font->m.elidedText(_ph, Qt::ElideRight, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1);
QPalette p(palette());
p.setColor(QPalette::Text, _st.textColor->c);
setPalette(p);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setFrameStyle(QFrame::NoFrame | QFrame::Plain);
viewport()->setAutoFillBackground(false);
setContentsMargins(0, 0, 0, 0);
switch (cScale()) {
case dbisOneAndQuarter: _fakeMargin = 1; break;
case dbisOneAndHalf: _fakeMargin = 2; break;
case dbisTwo: _fakeMargin = 4; break;
}
setStyleSheet(qsl("QTextEdit { margin: %1px; }").arg(_fakeMargin));
viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
_touchTimer.setSingleShot(true);
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int)));
connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged()));
}
void FlatTextarea::onTouchTimer() {
_touchRightButton = true;
}
bool FlatTextarea::viewportEvent(QEvent *e) {
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (ev->device()->type() == QTouchDevice::TouchScreen) {
touchEvent(ev);
return QTextEdit::viewportEvent(e);
}
}
return QTextEdit::viewportEvent(e);
}
void FlatTextarea::touchEvent(QTouchEvent *e) {
switch (e->type()) {
case QEvent::TouchBegin:
if (_touchPress || e->touchPoints().isEmpty()) return;
_touchTimer.start(QApplication::startDragTime());
_touchPress = true;
_touchMove = _touchRightButton = false;
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
break;
case QEvent::TouchUpdate:
if (!_touchPress || e->touchPoints().isEmpty()) return;
if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
_touchMove = true;
}
break;
case QEvent::TouchEnd:
if (!_touchPress) return;
if (!_touchMove && window()) {
Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton);
QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart));
if (_touchRightButton) {
QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart);
contextMenuEvent(&contextEvent);
}
}
_touchTimer.stop();
_touchPress = _touchMove = _touchRightButton = false;
break;
case QEvent::TouchCancel:
_touchPress = false;
_touchTimer.stop();
break;
}
}
QRect FlatTextarea::getTextRect() const {
return rect().marginsRemoved(_st.textMrg + st::textRectMargins);
}
void FlatTextarea::paintEvent(QPaintEvent *e) {
QPainter p(viewport());
p.fillRect(rect(), _st.bgColor->b);
bool phDraw = _phVisible;
if (animating()) {
p.setOpacity(a_phAlpha.current());
phDraw = true;
}
if (phDraw) {
p.save();
p.setClipRect(rect());
QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom());
p.setFont(_st.font->f);
p.setPen(a_phColor.current());
p.drawText(phRect, _ph, QTextOption(_st.phAlign));
p.restore();
}
QTextEdit::paintEvent(e);
}
void FlatTextarea::focusInEvent(QFocusEvent *e) {
a_phColor.start(_st.phFocusColor->c);
anim::start(this);
QTextEdit::focusInEvent(e);
}
void FlatTextarea::focusOutEvent(QFocusEvent *e) {
a_phColor.start(_st.phColor->c);
anim::start(this);
QTextEdit::focusOutEvent(e);
}
QSize FlatTextarea::sizeHint() const {
return geometry().size();
}
QSize FlatTextarea::minimumSizeHint() const {
return geometry().size();
}
QString FlatTextarea::getText(int32 start, int32 end) const {
if (end >= 0 && end <= start) return QString();
if (start < 0) start = 0;
bool full = (start == 0) && (end < 0);
QTextDocument *doc(document());
QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end);
if (till.isValid()) till = till.next();
int32 possibleLen = 0;
for (QTextBlock b = from; b != till; b = b.next()) {
possibleLen += b.length();
}
QString result;
result.reserve(possibleLen + 1);
if (!full && end < 0) {
end = possibleLen;
}
for (QTextBlock b = from; b != till; b = b.next()) {
for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) {
QTextFragment fragment(iter.fragment());
if (!fragment.isValid()) continue;
int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length());
if (!full) {
if (p >= end || e <= start) {
continue;
}
}
QTextCharFormat f = fragment.charFormat();
QString emojiText;
QString t(fragment.text());
if (!full) {
if (p < start) {
t = t.mid(start - p, end - start - p);
} else if (e > end) {
t = t.mid(0, end - p);
}
}
QChar *ub = t.data(), *uc = ub, *ue = uc + t.size();
for (; uc != ue; ++uc) {
switch (uc->unicode()) {
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
case QChar::ParagraphSeparator:
case QChar::LineSeparator:
*uc = QLatin1Char('\n');
break;
case QChar::Nbsp:
*uc = QLatin1Char(' ');
break;
case QChar::ObjectReplacementCharacter:
if (emojiText.isEmpty() && f.isImageFormat()) {
QString imageName = static_cast<QTextImageFormat*>(&f)->name();
if (imageName.midRef(0, 8) == qsl("emoji://")) {
uint32 index = imageName.mid(8).toUInt(0, 16);
const EmojiData *emoji = getEmoji(index);
if (emoji) {
emojiText.reserve(emoji->len);
switch (emoji->len) {
case 1: emojiText.append(QChar(emoji->code & 0xFFFF)); break;
case 2:
emojiText.append(QChar((emoji->code >> 16) & 0xFFFF));
emojiText.append(QChar(emoji->code & 0xFFFF));
break;
case 4:
emojiText.append(QChar((emoji->code >> 16) & 0xFFFF));
emojiText.append(QChar(emoji->code & 0xFFFF));
emojiText.append(QChar((emoji->code2 >> 16) & 0xFFFF));
emojiText.append(QChar(emoji->code2 & 0xFFFF));
break;
}
}
}
}
if (uc > ub) result.append(ub, uc - ub);
if (!emojiText.isEmpty()) result.append(emojiText);
ub = uc + 1;
break;
}
}
if (uc > ub) result.append(ub, uc - ub);
}
result.append('\n');
}
result.chop(1);
return result;
}
bool FlatTextarea::hasText() const {
QTextDocument *doc(document());
QTextBlock from = doc->begin(), till = doc->end();
if (from == till) return false;
for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) {
QTextFragment fragment(iter.fragment());
if (!fragment.isValid()) continue;
if (!fragment.text().isEmpty()) return true;
}
return (from.next() != till);
}
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
c.removeSelectedText();
QPixmap img(App::emojiSingle(emoji, _st.font->height));
QString url = qsl("emoji://") + QString::number(emoji->code, 16);
document()->addResource(QTextDocument::ImageResource, QUrl(url), QVariant(img));
QTextImageFormat imageFormat;
imageFormat.setWidth(img.width());
imageFormat.setHeight(img.height());
imageFormat.setName(url);
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
c.insertImage(imageFormat);
}
void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
int32 emojiPosition = 0;
const EmojiData *emoji = 0;
QTextDocument *doc(document());
while (true) {
int32 start = position, end = position + charsAdded;
QTextBlock from = doc->findBlock(start), till = doc->findBlock(end);
if (till.isValid()) till = till.next();
for (QTextBlock b = from; b != till; b = b.next()) {
for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) {
QTextFragment fragment(iter.fragment());
if (!fragment.isValid()) continue;
int32 p = fragment.position(), e = p + fragment.length();
if (p >= end || e <= start) {
continue;
}
QString t(fragment.text());
for (const QChar *ch = t.constData(), *e = ch + t.size(); ch != e; ++ch) {
if (ch + 1 < e && (ch->isHighSurrogate() || (ch->unicode() >= 48 && ch->unicode() < 58 || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3)) {
emoji = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
if (emoji) {
if (emoji->len == 4 && (ch + 3 >= e || (((ch + 2)->unicode() << 16) | (ch + 3)->unicode()) != emoji->code2)) {
emoji = 0;
} else {
emojiPosition = p + (ch - t.constData());
break;
}
}
++ch;
} else {
emoji = getEmoji(ch->unicode());
if (emoji) {
emojiPosition = p + (ch - t.constData());
break;
}
}
}
if (emoji) break;
}
if (emoji) break;
}
if (emoji) {
QTextCursor c(doc->docHandle(), emojiPosition);
c.setPosition(emojiPosition + emoji->len, QTextCursor::KeepAnchor);
int32 removedUpto = c.position();
insertEmoji(emoji, c);
for (Insertions::iterator i = _insertions.begin(), e = _insertions.end(); i != e; ++i) {
if (i->first >= removedUpto) {
i->first -= removedUpto - emojiPosition - 1;
} else if (i->first >= emojiPosition) {
i->second -= removedUpto - emojiPosition;
i->first = emojiPosition + 1;
} else if (i->first + i->second > emojiPosition + 1) {
i->second -= qMin(removedUpto, i->first + i->second) - emojiPosition;
}
}
charsAdded -= removedUpto - position;
position = emojiPosition + 1;
emoji = 0;
emojiPosition = 0;
} else {
break;
}
}
}
void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) {
if (_replacingEmojis || document()->availableRedoSteps() > 0) return;
const int takeBack = 3;
position -= takeBack;
charsAdded += takeBack;
if (position < 0) {
charsAdded += position;
position = 0;
}
if (charsAdded <= 0) return;
_insertions.push_back(Insertion(position, charsAdded));
}
void FlatTextarea::onDocumentContentsChanged() {
if (_replacingEmojis) return;
if (!_insertions.isEmpty()) {
if (document()->availableRedoSteps() > 0) {
_insertions.clear();
} else {
_replacingEmojis = true;
do {
Insertion i = _insertions.front();
_insertions.pop_front();
if (i.second > 0) {
processDocumentContentsChange(i.first, i.second);
}
} while (!_insertions.isEmpty());
_replacingEmojis = false;
}
}
QString curText(getText());
if (_oldtext != curText) {
_oldtext = curText;
emit changed();
}
updatePlaceholder();
}
bool FlatTextarea::animStep(float64 ms) {
float dt = ms / _st.phDuration;
bool res = true;
if (dt >= 1) {
res = false;
a_phLeft.finish();
a_phAlpha.finish();
a_phColor.finish();
a_phLeft = anim::ivalue(a_phLeft.current());
a_phAlpha = anim::fvalue(a_phAlpha.current());
a_phColor = anim::cvalue(a_phColor.current());
} else {
a_phLeft.update(dt, _st.phLeftFunc);
a_phAlpha.update(dt, _st.phAlphaFunc);
a_phColor.update(dt, _st.phColorFunc);
}
update();
return res;
}
void FlatTextarea::updatePlaceholder() {
bool vis = !hasText();
if (vis == _phVisible) return;
a_phLeft.start(vis ? 0 : _st.phShift);
a_phAlpha.start(vis ? 1 : 0);
anim::start(this);
_phVisible = vis;
}
QMimeData *FlatTextarea::createMimeDataFromSelection() const {
QMimeData *result = new QMimeData();
QTextCursor c(textCursor());
int32 start = c.selectionStart(), end = c.selectionEnd();
if (end > start) {
result->setText(getText(start, end));
}
return result;
}
void FlatTextarea::keyPressEvent(QKeyEvent *e) {
bool shift = e->modifiers().testFlag(Qt::ShiftModifier);
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier), ctrlGood = ctrl && cCtrlEnter() || !ctrl && !shift && !cCtrlEnter();
bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return);
if (enter && ctrlGood) {
emit submitted();
} else if (e->key() == Qt::Key_Escape) {
emit cancelled();
} else if (e->key() == Qt::Key_Tab) {
emit tabbed();
} else {
QTextCursor tc(textCursor());
if (enter && ctrl) {
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
}
QTextEdit::keyPressEvent(e);
if (tc == textCursor()) {
bool check = false;
if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
tc.movePosition(QTextCursor::Start);
check = true;
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
tc.movePosition(QTextCursor::End);
check = true;
}
if (check) {
if (tc == textCursor()) {
e->ignore();
} else {
setTextCursor(tc);
}
}
}
}
}
void FlatTextarea::resizeEvent(QResizeEvent *e) {
_phelided = _st.font->m.elidedText(_ph, Qt::ElideRight, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1);
QTextEdit::resizeEvent(e);
}
void FlatTextarea::mousePressEvent(QMouseEvent *e) {
QTextEdit::mousePressEvent(e);
}

View File

@@ -0,0 +1,94 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtWidgets/QTextEdit>
#include "style.h"
#include "animation.h"
class FlatTextarea : public QTextEdit, public Animated {
Q_OBJECT
public:
FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString());
QString val() const;
bool viewportEvent(QEvent *e);
void touchEvent(QTouchEvent *e);
void paintEvent(QPaintEvent *e);
void focusInEvent(QFocusEvent *e);
void focusOutEvent(QFocusEvent *e);
void keyPressEvent(QKeyEvent *e);
void resizeEvent(QResizeEvent *e);
void mousePressEvent(QMouseEvent *e);
void updatePlaceholder();
QRect getTextRect() const;
bool animStep(float64 ms);
QSize sizeHint() const;
QSize minimumSizeHint() const;
QString getText(int32 start = 0, int32 end = -1) const;
bool hasText() const;
public slots:
void onTouchTimer();
void onDocumentContentsChange(int position, int charsRemoved, int charsAdded);
void onDocumentContentsChanged();
signals:
void changed();
void submitted();
void cancelled();
void tabbed();
protected:
void insertEmoji(EmojiPtr emoji, QTextCursor c);
private:
void processDocumentContentsChange(int position, int charsAdded);
QMimeData *createMimeDataFromSelection() const;
QString _ph, _phelided, _oldtext;
bool _phVisible;
anim::ivalue a_phLeft;
anim::fvalue a_phAlpha;
anim::cvalue a_phColor;
style::flatTextarea _st;
int32 _fakeMargin;
QTimer _touchTimer;
bool _touchPress, _touchRightButton, _touchMove;
QPoint _touchStart;
bool _replacingEmojis;
typedef QPair<int, int> Insertion;
typedef QList<Insertion> Insertions;
Insertions _insertions;
};

View File

@@ -0,0 +1,295 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "gui/images.h"
#include "mainwidget.h"
namespace {
typedef QMap<QString, LocalImage*> LocalImages;
LocalImages localImages;
Image *blank() {
static Image *img = getImage(qsl(":/gui/art/blank.gif"));
return img;
}
typedef QMap<QByteArray, StorageImage*> StorageImages;
StorageImages storageImages;
QByteArray storageKey(int32 dc, const int64 &volume, int32 local, const int64 &secret) {
QByteArray result(24, Qt::Uninitialized);
memcpy(result.data(), &dc, 4);
memcpy(result.data() + 4, &volume, 8);
memcpy(result.data() + 12, &local, 4);
memcpy(result.data() + 16, &secret, 8);
return result;
}
int64 globalAquiredSize = 0;
}
bool Image::isNull() const {
return (this == blank());
}
ImagePtr::ImagePtr() : Parent(blank()) {
}
ImagePtr::ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def) :
Parent((location.type() == mtpc_fileLocation) ? (Image*)(getImage(width, height, location.c_fileLocation().vdc_id.v, location.c_fileLocation().vvolume_id.v, location.c_fileLocation().vlocal_id.v, location.c_fileLocation().vsecret.v)) : def.v()) {
}
const QPixmap &Image::pix(int32 w, int32 h) const {
restore();
checkload();
if (w <= 0 || !width() || !height()) w = width();
uint64 k = (uint64(w) << 32) | uint64(h);
Sizes::const_iterator i = _sizesCache.constFind(k);
if (i == _sizesCache.cend()) {
QPixmap p(pixNoCache(w, h, true));
i = _sizesCache.insert(k, p);
if (!p.isNull()) {
globalAquiredSize += int64(p.width()) * p.height() * 4;
}
}
return i.value();
}
QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth) const {
restore();
loaded();
const QPixmap &p(pixData());
if (p.isNull()) return blank()->pix();
if (w <= 0 || !width() || !height() || w == width()) return p;
if (h <= 0) {
return QPixmap::fromImage(p.toImage().scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation));
}
return QPixmap::fromImage(p.toImage().scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation));
}
void Image::forget() const {
if (forgot) return;
const QPixmap &p(pixData());
if (p.isNull()) return;
invalidateSizeCache();
if (saved.isEmpty()) {
QBuffer buffer(&saved);
p.save(&buffer, format);
}
globalAquiredSize -= int64(p.width()) * p.height() * 4;
doForget();
forgot = true;
}
void Image::restore() const {
if (!forgot) return;
doRestore();
const QPixmap &p(pixData());
if (!p.isNull()) {
globalAquiredSize += int64(p.width()) * p.height() * 4;
}
forgot = false;
}
void Image::invalidateSizeCache() const {
for (Sizes::const_iterator i = _sizesCache.cbegin(), e = _sizesCache.cend(); i != e; ++i) {
if (!i->isNull()) {
globalAquiredSize -= int64(i->width()) * i->height() * 4;
}
}
_sizesCache.clear();
}
LocalImage::LocalImage(const QString &file) : data(file) {
if (!data.isNull()) {
globalAquiredSize += int64(data.width()) * data.height() * 4;
}
}
LocalImage::LocalImage(const QPixmap &pixmap, QByteArray format) : Image(format), data(pixmap) {
if (!data.isNull()) {
globalAquiredSize += int64(data.width()) * data.height() * 4;
}
}
const QPixmap &LocalImage::pixData() const {
return data;
}
int32 LocalImage::width() const {
restore();
return data.width();
}
int32 LocalImage::height() const {
restore();
return data.height();
}
LocalImage *getImage(const QString &file) {
LocalImages::const_iterator i = localImages.constFind(file);
if (i == localImages.cend()) {
i = localImages.insert(file, new LocalImage(file));
}
return i.value();
}
LocalImage::~LocalImage() {
if (!data.isNull()) {
globalAquiredSize -= int64(data.width()) * data.height() * 4;
}
}
LocalImage *getImage(const QPixmap &pixmap, QByteArray format) {
return new LocalImage(pixmap, format);
}
void clearStorageImages() {
for (StorageImages::const_iterator i = storageImages.cbegin(), e = storageImages.cend(); i != e; ++i) {
delete i.value();
}
storageImages.clear();
}
void clearAllImages() {
for (LocalImages::const_iterator i = localImages.cbegin(), e = localImages.cend(); i != e; ++i) {
delete i.value();
}
localImages.clear();
clearStorageImages();
}
int64 imageCacheSize() {
return globalAquiredSize;
}
StorageImage::StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret) : loader(new mtpFileLoader(dc, volume, local, secret)), w(width), h(height) {
}
StorageImage::StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, QByteArray &bytes) : loader(0), w(width), h(height) {
setData(bytes);
}
const QPixmap &StorageImage::pixData() const {
return data;
}
int32 StorageImage::width() const {
return w;
}
int32 StorageImage::height() const {
return h;
}
bool StorageImage::check() const {
if (loader->done()) {
switch (loader->fileType()) {
case mtpc_storage_fileGif: format = "GIF"; break;
case mtpc_storage_fileJpeg: format = "JPG"; break;
case mtpc_storage_filePng: format = "PNG"; break;
default: format = QByteArray(); break;
}
if (!data.isNull()) {
globalAquiredSize -= int64(data.width()) * data.height() * 4;
}
QByteArray bytes = loader->bytes();
data = QPixmap::fromImage(App::readImage(bytes, &format), Qt::ColorOnly);
if (!data.isNull()) {
globalAquiredSize += int64(data.width()) * data.height() * 4;
}
w = data.width();
h = data.height();
invalidateSizeCache();
loader->deleteLater();
loader = 0;
saved = bytes;
forgot = false;
return true;
}
return false;
}
void StorageImage::setData(QByteArray &bytes, const QByteArray &format) {
QBuffer buffer(&bytes);
QImageReader reader(&buffer, format);
if (!data.isNull()) {
globalAquiredSize -= int64(data.width()) * data.height() * 4;
}
data = QPixmap::fromImageReader(&reader, Qt::ColorOnly);
if (!data.isNull()) {
globalAquiredSize += int64(data.width()) * data.height() * 4;
}
w = data.width();
h = data.height();
invalidateSizeCache();
if (loader) {
loader->deleteLater();
loader = 0;
}
this->saved = bytes;
this->format = reader.format();
forgot = false;
}
StorageImage::~StorageImage() {
if (!data.isNull()) {
globalAquiredSize -= int64(data.width()) * data.height() * 4;
}
if (loader) {
loader->deleteLater();
loader = 0;
}
}
bool StorageImage::loaded() const {
if (!loader) return true;
return check();
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret));
}
return i.value();
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
QByteArray bytesArr(bytes);
i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, bytesArr));
} else if (!i.value()->loaded()) {
QByteArray bytesArr(bytes);
i.value()->setData(bytesArr);
}
return i.value();
}

View File

@@ -0,0 +1,168 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtGui/QPixmap>
class Image {
public:
Image(QByteArray format = "PNG") : forgot(false), format(format) {
}
virtual bool loaded() const {
return true;
}
const QPixmap &pix(int32 w = 0, int32 h = 0) const;
QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false) const;
virtual int32 width() const = 0;
virtual int32 height() const = 0;
virtual void load(bool loadFirst = false, bool prior = true) {
}
virtual void checkload() const {
}
bool isNull() const;
void forget() const;
void restore() const;
virtual ~Image() {
invalidateSizeCache();
}
protected:
virtual const QPixmap &pixData() const = 0;
virtual void doForget() const = 0;
virtual void doRestore() const = 0;
void invalidateSizeCache() const;
mutable QByteArray saved, format;
mutable bool forgot;
private:
typedef QMap<uint64, QPixmap> Sizes;
mutable Sizes _sizesCache;
};
class LocalImage : public Image {
public:
LocalImage(const QString &file);
LocalImage(const QPixmap &pixmap, QByteArray format);
int32 width() const;
int32 height() const;
~LocalImage();
protected:
const QPixmap &pixData() const;
void doForget() const {
data = QPixmap();
}
void doRestore() const {
QBuffer buffer(&saved);
QImageReader reader(&buffer, format);
data = QPixmap::fromImageReader(&reader, Qt::ColorOnly);
}
private:
mutable QPixmap data;
};
LocalImage *getImage(const QString &file);
LocalImage *getImage(const QPixmap &pixmap, QByteArray format);
class StorageImage : public Image {
public:
StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret);
StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, QByteArray &bytes);
int32 width() const;
int32 height() const;
bool loaded() const;
void setData(QByteArray &bytes, const QByteArray &format = "JPG");
void load(bool loadFirst = false, bool prior = true) {
if (loader) {
loader->start(loadFirst, prior);
check();
}
}
void checkload() const {
if (loader) {
if (!loader->loading()) {
loader->start(true);
}
check();
}
}
~StorageImage();
protected:
const QPixmap &pixData() const;
bool check() const;
void doForget() const {
data = QPixmap();
}
void doRestore() const {
QBuffer buffer(&saved);
QImageReader reader(&buffer, format);
data = QPixmap::fromImageReader(&reader, Qt::ColorOnly);
}
private:
mutable QPixmap data;
mutable int32 w, h;
mutable mtpFileLoader *loader;
};
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret);
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes);
Image *getImage(int32 width, int32 height, const MTPFileLocation &location);
class ImagePtr : public ManagedPtr<Image> {
public:
ImagePtr();
ImagePtr(const QString &file) : Parent(getImage(file)) {
}
ImagePtr(const QPixmap &pixmap, QByteArray format) : Parent(getImage(pixmap, format)) {
}
ImagePtr(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret) : Parent(getImage(width, height, dc, volume, local, secret)) {
}
ImagePtr(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) : Parent(getImage(width, height, dc, volume, local, secret, bytes)) {
}
ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def = ImagePtr());
};
void clearStorageImages();
void clearAllImages();
int64 imageCacheSize();

View File

@@ -0,0 +1,96 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "gui/phoneinput.h"
PhoneInput::PhoneInput(QWidget *parent, const style::flatInput &st, const QString &ph) : FlatInput(parent, st, ph) {
}
void PhoneInput::correctValue(QKeyEvent *e, const QString &was) {
if (e && e->key() == Qt::Key_Backspace && !was.length()) {
emit voidBackspace(e);
return;
}
QString oldText(text()), newText;
int oldPos(cursorPosition()), newPos(-1), oldLen(oldText.length()), digitCount = 0;
for (int i = 0; i < oldLen; ++i) {
if (oldText[i].isDigit()) {
++digitCount;
}
}
if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength;
bool strict = (digitCount == MaxPhoneTailLength);
newText.reserve(oldLen);
for (int i = 0; i < oldLen; ++i) {
QChar ch(oldText[i]);
if (ch.isDigit()) {
if (!digitCount--) {
break;
}
newText += ch;
if (strict && !digitCount) {
break;
}
} else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
newText += ch;
}
if (i == oldPos) {
newPos = newText.length();
}
}
if (newPos < 0) {
newPos = newText.length();
}
if (newText != oldText) {
setText(newText);
if (newPos != oldPos) {
setCursorPosition(newPos);
}
}
}
void PhoneInput::addedToNumber(const QString &added) {
setFocus();
QString was(text());
setText(added + text());
setCursorPosition(added.length());
correctValue(0, was);
updatePlaceholder();
}
PortInput::PortInput(QWidget *parent, const style::flatInput &st, const QString &ph, const QString &val) : FlatInput(parent, st, ph, val) {
correctValue(0, QString());
}
void PortInput::correctValue(QKeyEvent *e, const QString &was) {
QString oldText(text()), newText(oldText);
newText.replace(QRegularExpression(qsl("[^\\d]")), QString());
if (!newText.toInt()) {
newText = QString();
} else if (newText.toInt() > 65535) {
newText = was;
}
if (newText != oldText) {
setText(newText);
updatePlaceholder();
}
}

View File

@@ -0,0 +1,54 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/flatinput.h"
class PhoneInput : public FlatInput {
Q_OBJECT
public:
PhoneInput(QWidget *parent, const style::flatInput &st, const QString &ph);
public slots:
void addedToNumber(const QString &added);
signals:
void voidBackspace(QKeyEvent *e);
protected:
void correctValue(QKeyEvent *e, const QString &was);
};
class PortInput : public FlatInput {
Q_OBJECT
public:
PortInput(QWidget *parent, const style::flatInput &st, const QString &ph, const QString &val);
protected:
void correctValue(QKeyEvent *e, const QString &was);
};

View File

@@ -0,0 +1,597 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "style.h"
#include "gui/scrollarea.h"
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
ScrollShadow::ScrollShadow(ScrollArea *parent, const style::flatScroll *st) : QWidget(parent), _st(st) {
setVisible(false);
}
void ScrollShadow::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), _st->shColor->b);
}
void ScrollShadow::changeVisibility(bool shown) {
setVisible(shown);
}
ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::flatScroll *st) : QWidget(parent),
_st(st), _area(parent), _vertical(vert), _hideIn(-1),
_over(false), _overbar(false), _moving(false), _topSh(false), _bottomSh(false),
a_bg((_st->hiding ? st::transparent : _st->bgColor)->c), a_bar((_st->hiding ? st::transparent : _st->barColor)->c),
_connected(vert ? parent->verticalScrollBar() : parent->horizontalScrollBar()), _scrollMax(_connected->maximum()) {
recountSize();
_hideTimer.setSingleShot(true);
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideTimer()));
connect(_connected, SIGNAL(valueChanged(int)), this, SLOT(updateBar()));
connect(_connected, SIGNAL(rangeChanged(int, int)), this, SLOT(updateBar()));
updateBar();
}
void ScrollBar::recountSize() {
setGeometry(_vertical ? QRect(_area->width() - _st->width, 0, _st->width, _area->height()) : QRect(0, _area->height() - _st->width, _area->width(), _st->width));
}
void ScrollBar::updateBar() {
QRect newBar;
if (_connected->maximum() != _scrollMax) {
int32 oldMax = _scrollMax, newMax = _connected->maximum();
_scrollMax = newMax;
_area->rangeChanged(oldMax, newMax, _vertical);
}
if (_vertical) {
int sh = _area->scrollHeight(), rh = height() - 2 * _st->deltay, h = sh ? int32((rh * int64(_area->height())) / sh) : 0;
if (h >= rh || !_area->scrollTopMax() || rh < _st->minHeight) {
if (!isHidden()) hide();
if (_topSh) emit topShadowVisibility(_topSh = (_st->topsh < 0));
if (_bottomSh) emit bottomShadowVisibility(_bottomSh = (_st->bottomsh < 0));
return;
}
if (h <= _st->minHeight) h = _st->minHeight;
int stm = _area->scrollTopMax(), y = stm ? int32(((rh - h) * int64(_area->scrollTop())) / stm) : 0;
if (y > rh - h) y = rh - h;
newBar = QRect(_st->deltax, y + _st->deltay, width() - 2 * _st->deltax, h);
} else {
int sw = _area->scrollWidth(), rw = width() - 2 * _st->deltay, w = sw ? int32((rw * int64(_area->width())) / sw) : 0;
if (w >= rw || !_area->scrollLeftMax() || rw < _st->minHeight) {
if (!isHidden()) hide();
return;
}
if (w <= _st->minHeight) w = _st->minHeight;
int slm = _area->scrollLeftMax(), x = slm ? int32(((rw - w) * int64(_area->scrollLeft())) / slm) : 0;
if (x > rw - w) x = rw - w;
newBar = QRect(x + _st->deltay, _st->deltax, w, height() - 2 * _st->deltax);
}
if (newBar != _bar) {
_bar = newBar;
update();
}
if (_vertical) {
bool newTopSh = (_st->topsh < 0) || (_area->scrollTop() > _st->topsh), newBottomSh = (_st->bottomsh < 0) || (_area->scrollTop() < _area->scrollTopMax() - _st->bottomsh);
if (newTopSh != _topSh) emit topShadowVisibility(_topSh = newTopSh);
if (newBottomSh != _bottomSh) emit bottomShadowVisibility(_bottomSh = newBottomSh);
}
if (isHidden()) show();
}
void ScrollBar::onHideTimer() {
_hideIn = -1;
a_bg.start(st::transparent->c);
a_bar.start(st::transparent->c);
anim::start(this);
}
void ScrollBar::paintEvent(QPaintEvent *e) {
if (!_bar.width() && !_bar.height()) {
hide();
return;
}
if (!a_bg.current().alpha() && !a_bar.current().alpha()) return;
QPainter p(this);
int32 deltax = _vertical ? _st->deltax : _st->deltay, deltay = _vertical ? _st->deltay : _st->deltax;
p.setPen(Qt::NoPen);
if (_st->round) {
p.setBrush(a_bg.current());
p.drawRoundedRect(QRect(deltax, deltay, width() - 2 * deltax, height() - 2 * deltay), _st->round, _st->round);
p.setBrush(a_bar.current());
p.drawRoundedRect(_bar, _st->round, _st->round);
} else {
p.fillRect(QRect(deltax, deltay, width() - 2 * deltax, height() - 2 * deltay), a_bg.current());
p.fillRect(_bar, a_bar.current());
}
}
bool ScrollBar::animStep(float64 ms) {
float64 dt = ms / _st->duration;
bool res = true;
if (dt >= 1) {
a_bg.finish();
a_bar.finish();
res = false;
} else {
a_bg.update(dt, anim::linear);
a_bar.update(dt, anim::linear);
}
update();
return res;
}
void ScrollBar::hideTimeout(int64 dt) {
if (_hideIn < 0) {
a_bg.start((_over ? _st->bgOverColor : _st->bgColor)->c);
a_bar.start((_overbar ? _st->barOverColor : _st->barColor)->c);
anim::start(this);
}
_hideIn = dt;
if (!_moving && _hideIn >= 0) {
_hideTimer.start(_hideIn);
}
}
void ScrollBar::enterEvent(QEvent *e) {
_hideTimer.stop();
setMouseTracking(true);
_over = true;
a_bg.start(_st->bgOverColor->c);
a_bar.start(_st->barColor->c);
anim::start(this);
}
void ScrollBar::leaveEvent(QEvent *e) {
if (!_moving) {
setMouseTracking(false);
a_bg.start(_st->bgColor->c);
a_bar.start(_st->barColor->c);
anim::start(this);
if (_hideIn >= 0) {
_hideTimer.start(_hideIn);
}
}
_over = _overbar = false;
}
void ScrollBar::mouseMoveEvent(QMouseEvent *e) {
bool newOverBar = _bar.contains(e->pos());
if (_overbar != newOverBar) {
_overbar = newOverBar;
if (!_moving) {
a_bar.start((newOverBar ? _st->barOverColor : _st->barColor)->c);
a_bg.start(_st->bgOverColor->c);
anim::start(this);
}
}
if (_moving) {
int delta = 0, barDelta = _vertical ? (_area->height() - _bar.height()) : (_area->width() - _bar.width());
if (barDelta) {
QPoint d = (e->globalPos() - _dragStart);
delta = int32((_vertical ? (d.y() * int64(_area->scrollTopMax())) : (d.x() * int64(_area->scrollLeftMax()))) / barDelta);
}
_connected->setValue(_startFrom + delta);
}
}
void ScrollBar::mousePressEvent(QMouseEvent *e) {
if (!width() || !height()) return;
_dragStart = e->globalPos();
_moving = true;
if (_overbar) {
_startFrom = _connected->value();
} else {
_startFrom = _vertical ? int32((e->pos().y() * int64(_area->scrollTopMax())) / height()) : ((e->pos().x() * int64(_area->scrollLeftMax())) / width());
_connected->setValue(_startFrom);
if (!_overbar) {
_overbar = true;
a_bar.start(_st->barOverColor->c);
a_bg.start(_st->bgOverColor->c);
anim::start(this);
}
}
emit _area->scrollStarted();
}
void ScrollBar::mouseReleaseEvent(QMouseEvent *e) {
if (_moving) {
_moving = false;
bool a = false;
if (!_overbar) {
if (!_over || _hideIn) {
a_bar.start(_st->barColor->c);
a = true;
}
}
if (!_over) {
if (_hideIn) {
a_bg.start(_st->bgColor->c);
a = true;
}
if (_hideIn >= 0) {
_hideTimer.start(_hideIn);
}
}
if (a) anim::start(this);
emit _area->scrollFinished();
}
if (!_over) {
setMouseTracking(false);
}
}
void ScrollBar::resizeEvent(QResizeEvent *e) {
updateBar();
}
ScrollArea::ScrollArea(QWidget *parent, const style::flatScroll &st, bool handleTouch) : QScrollArea(parent), _st(st), _touchEnabled(handleTouch),
hor(this, false, &_st), vert(this, true, &_st), topSh(this, &_st), bottomSh(this, &_st),
_touchScroll(false), _touchPress(false), _touchRightButton(false), _widgetAcceptsTouch(false),
_touchScrollState(TouchScrollManual), _touchPrevPosValid(false), _touchWaitingAcceleration(false), _touchSpeedTime(0), _touchAccelerationTime(0), _touchTime(0) {
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrolled()));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrolled()));
connect(&vert, SIGNAL(topShadowVisibility(bool)), &topSh, SLOT(changeVisibility(bool)));
connect(&vert, SIGNAL(bottomShadowVisibility(bool)), &bottomSh, SLOT(changeVisibility(bool)));
if (_st.hiding) {
connect(this, SIGNAL(scrolled()), this, SLOT(onScrolled()));
}
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setFrameStyle(QFrame::NoFrame | QFrame::Plain);
viewport()->setAutoFillBackground(false);
_horValue = horizontalScrollBar()->value();
_vertValue = verticalScrollBar()->value();
if (_touchEnabled) {
viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
_touchTimer.setSingleShot(true);
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer()));
}
}
void ScrollArea::touchDeaccelerate(int32 elapsed) {
int32 x = _touchSpeed.x();
int32 y = _touchSpeed.y();
_touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed));
_touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed));
}
void ScrollArea::onScrolled() {
int32 horValue = horizontalScrollBar()->value(), vertValue = verticalScrollBar()->value();
if (_horValue != horValue) {
_horValue = horValue;
if (_st.hiding) {
hor.hideTimeout(_st.hiding);
}
}
if (_vertValue != vertValue) {
_vertValue = vertValue;
if (_st.hiding) {
vert.hideTimeout(_st.hiding);
}
}
}
int ScrollArea::scrollWidth() const {
return scrollLeftMax() + width();
}
int ScrollArea::scrollHeight() const {
return scrollTopMax() + height();
}
int ScrollArea::scrollLeftMax() const {
return horizontalScrollBar()->maximum();
}
int ScrollArea::scrollTopMax() const {
return verticalScrollBar()->maximum();
}
int ScrollArea::scrollLeft() const {
return horizontalScrollBar()->value();
}
int ScrollArea::scrollTop() const {
return verticalScrollBar()->value();
}
void ScrollArea::onTouchTimer() {
_touchRightButton = true;
}
void ScrollArea::onTouchScrollTimer() {
uint64 nowTime = getms();
if (_touchScrollState == TouchScrollAcceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) {
_touchScrollState = TouchScrollManual;
touchResetSpeed();
} else if (_touchScrollState == TouchScrollAuto || _touchScrollState == TouchScrollAcceleration) {
int32 elapsed = int32(nowTime - _touchTime);
QPoint delta = _touchSpeed * elapsed / 1000;
bool hasScrolled = touchScroll(delta);
if (_touchSpeed.isNull() || !hasScrolled) {
_touchScrollState = TouchScrollManual;
_touchScroll = false;
_touchScrollTimer.stop();
} else {
_touchTime = nowTime;
}
touchDeaccelerate(elapsed);
}
}
void ScrollArea::touchUpdateSpeed() {
const uint64 nowTime = getms();
if (_touchPrevPosValid) {
const int elapsed = nowTime - _touchSpeedTime;
if (elapsed) {
const QPoint newPixelDiff = (_touchPos - _touchPrevPos);
const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed);
// fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
// of a small horizontal offset when scrolling vertically
const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
if (_touchScrollState == TouchScrollAuto) {
const int oldSpeedY = _touchSpeed.y();
const int oldSpeedX = _touchSpeed.x();
if ((oldSpeedY <= 0 && newSpeedY <= 0) || (oldSpeedY >= 0 && newSpeedY >= 0)
&& (oldSpeedX <= 0 && newSpeedX <= 0) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
_touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
_touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
} else {
_touchSpeed = QPoint();
}
} else {
// we average the speed to avoid strange effects with the last delta
if (!_touchSpeed.isNull()) {
_touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
_touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
} else {
_touchSpeed = QPoint(newSpeedX, newSpeedY);
}
}
}
} else {
_touchPrevPosValid = true;
}
_touchSpeedTime = nowTime;
_touchPrevPos = _touchPos;
}
void ScrollArea::touchResetSpeed() {
_touchSpeed = QPoint();
_touchPrevPosValid = false;
}
bool ScrollArea::eventFilter(QObject *obj, QEvent *e) {
bool res = QScrollArea::eventFilter(obj, e);
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (_touchEnabled && ev->device()->type() == QTouchDevice::TouchScreen) {
if (obj == widget()) {
touchEvent(ev);
return true;
}
}
}
return res;
}
bool ScrollArea::viewportEvent(QEvent *e) {
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (_touchEnabled && ev->device()->type() == QTouchDevice::TouchScreen) {
touchEvent(ev);
return true;
}
}
return QScrollArea::viewportEvent(e);
}
void ScrollArea::touchEvent(QTouchEvent *e) {
if (!e->touchPoints().isEmpty()) {
_touchPrevPos = _touchPos;
_touchPos = e->touchPoints().cbegin()->screenPos().toPoint();
}
switch (e->type()) {
case QEvent::TouchBegin:
if (_touchPress || e->touchPoints().isEmpty()) return;
_touchPress = true;
if (_touchScrollState == TouchScrollAuto) {
_touchScrollState = TouchScrollAcceleration;
_touchWaitingAcceleration = true;
_touchAccelerationTime = getms();
touchUpdateSpeed();
_touchStart = _touchPos;
} else {
_touchScroll = false;
_touchTimer.start(QApplication::startDragTime());
}
_touchStart = _touchPrevPos = _touchPos;
_touchRightButton = false;
break;
case QEvent::TouchUpdate:
if (!_touchPress) return;
if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
_touchTimer.stop();
_touchScroll = true;
touchUpdateSpeed();
}
if (_touchScroll) {
if (_touchScrollState == TouchScrollManual) {
touchScrollUpdated(_touchPos);
} else if (_touchScrollState == TouchScrollAcceleration) {
touchUpdateSpeed();
_touchAccelerationTime = getms();
if (_touchSpeed.isNull()) {
_touchScrollState = TouchScrollManual;
}
}
}
break;
case QEvent::TouchEnd:
if (!_touchPress) return;
_touchPress = false;
if (_touchScroll) {
if (_touchScrollState == TouchScrollManual) {
_touchScrollState = TouchScrollAuto;
_touchPrevPosValid = false;
_touchScrollTimer.start(15);
_touchTime = getms();
} else if (_touchScrollState == TouchScrollAuto) {
_touchScrollState = TouchScrollManual;
_touchScroll = false;
touchResetSpeed();
} else if (_touchScrollState == TouchScrollAcceleration) {
_touchScrollState = TouchScrollAuto;
_touchWaitingAcceleration = false;
_touchPrevPosValid = false;
}
} else if (window() && widget()) { // one short tap -- like left mouse click, one long tap -- like right mouse click
#ifdef Q_OS_WIN
Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton);
QPoint mapped(widget()->mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart));
QMouseEvent pressEvent(QEvent::MouseButtonPress, mapped, winMapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers());
pressEvent.accept();
qt_sendSpontaneousEvent(widget(), &pressEvent);
QMouseEvent releaseEvent(QEvent::MouseButtonRelease, mapped, winMapped, _touchStart, btn, Qt::MouseButtons(btn), Qt::KeyboardModifiers());
qt_sendSpontaneousEvent(widget(), &releaseEvent);
if (_touchRightButton) {
QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart);
qt_sendSpontaneousEvent(widget(), &contextEvent);
}
#endif
}
_touchTimer.stop();
_touchRightButton = false;
break;
case QEvent::TouchCancel:
_touchPress = false;
_touchScroll = false;
_touchScrollState = TouchScrollManual;
_touchTimer.stop();
break;
}
}
void ScrollArea::touchScrollUpdated(const QPoint &screenPos) {
_touchPos = screenPos;
touchScroll(_touchPos - _touchPrevPos);
touchUpdateSpeed();
}
bool ScrollArea::touchScroll(const QPoint &delta) {
int32 scTop = scrollTop(), scMax = scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax);
if (scNew == scTop) return false;
scrollToY(scNew);
return true;
}
void ScrollArea::resizeEvent(QResizeEvent *e) {
QScrollArea::resizeEvent(e);
hor.recountSize();
vert.recountSize();
topSh.setGeometry(QRect(0, 0, width(), qAbs(_st.topsh)));
bottomSh.setGeometry(QRect(0, height() - qAbs(_st.bottomsh), width(), qAbs(_st.bottomsh)));
emit geometryChanged();
}
void ScrollArea::moveEvent(QMoveEvent *e) {
QScrollArea::moveEvent(e);
emit geometryChanged();
}
void ScrollArea::enterEvent(QEvent *e) {
if (_st.hiding) {
hor.hideTimeout(_st.hiding);
vert.hideTimeout(_st.hiding);
}
}
void ScrollArea::leaveEvent(QEvent *e) {
if (_st.hiding) {
hor.hideTimeout(0);
vert.hideTimeout(0);
}
}
void ScrollArea::scrollToY(int toTop, int toBottom) {
int toMin = 0, toMax = scrollTopMax();
if (toTop < toMin) {
toTop = toMin;
} else if (toTop > toMax) {
toTop = toMax;
}
bool exact = (toBottom < 0);
int curTop = scrollTop(), curHeight = height(), curBottom = curTop + curHeight, scToTop = toTop;
if (!exact && toTop >= curTop) {
if (toBottom < toTop) toBottom = toTop;
if (toBottom <= curBottom) return;
scToTop = toBottom - curHeight;
if (scToTop > toTop) scToTop = toTop;
if (scToTop == curTop) return;
} else {
scToTop = toTop;
}
verticalScrollBar()->setValue(scToTop);
}
void ScrollArea::setWidget(QWidget *w) {
if (widget() && _touchEnabled) {
widget()->removeEventFilter(this);
if (!_widgetAcceptsTouch) widget()->setAttribute(Qt::WA_AcceptTouchEvents, false);
}
QScrollArea::setWidget(w);
if (w) {
w->setAutoFillBackground(false);
if (_touchEnabled) {
w->installEventFilter(this);
_widgetAcceptsTouch = w->testAttribute(Qt::WA_AcceptTouchEvents);
w->setAttribute(Qt::WA_AcceptTouchEvents);
}
}
}
void ScrollArea::rangeChanged(int oldMax, int newMax, bool vertical) {
}

View File

@@ -0,0 +1,174 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include <QtWidgets/QScrollArea>
#include "style.h"
enum TouchScrollState {
TouchScrollManual, // Scrolling manually with the finger on the screen
TouchScrollAuto, // Scrolling automatically
TouchScrollAcceleration // Scrolling automatically but a finger is on the screen
};
class ScrollArea;
class ScrollShadow : public QWidget {
Q_OBJECT
public:
ScrollShadow(ScrollArea *parent, const style::flatScroll *st);
void paintEvent(QPaintEvent *e);
public slots:
void changeVisibility(bool shown);
private:
const style::flatScroll *_st;
};
class ScrollBar : public QWidget, public Animated {
Q_OBJECT
public:
ScrollBar(ScrollArea *parent, bool vertical, const style::flatScroll *st);
void recountSize();
void paintEvent(QPaintEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void resizeEvent(QResizeEvent *e);
bool animStep(float64 ms);
void hideTimeout(int64 dt);
public slots:
void updateBar();
void onHideTimer();
signals:
void topShadowVisibility(bool);
void bottomShadowVisibility(bool);
private:
ScrollArea *_area;
const style::flatScroll *_st;
bool _vertical;
bool _over, _overbar, _moving;
bool _topSh, _bottomSh;
QPoint _dragStart;
QScrollBar *_connected;
int32 _startFrom, _scrollMax;
int64 _hideIn;
QTimer _hideTimer;
anim::cvalue a_bg, a_bar;
QRect _bar;
};
class ScrollArea : public QScrollArea {
Q_OBJECT
public:
ScrollArea(QWidget *parent, const style::flatScroll &st = st::scrollDef, bool handleTouch = true);
bool viewportEvent(QEvent *e);
void touchEvent(QTouchEvent *e);
bool eventFilter(QObject *obj, QEvent *e);
void resizeEvent(QResizeEvent *e);
void moveEvent(QMoveEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
int scrollWidth() const;
int scrollHeight() const;
int scrollLeftMax() const;
int scrollTopMax() const;
int scrollLeft() const;
int scrollTop() const;
void setWidget(QWidget *widget);
void rangeChanged(int oldMax, int newMax, bool vertical);
public slots:
void scrollToY(int toTop, int toBottom = -1);
void onScrolled();
void onTouchTimer();
void onTouchScrollTimer();
signals:
void scrolled();
void scrollStarted();
void scrollFinished();
void geometryChanged();
private:
bool touchScroll(const QPoint &delta);
void touchScrollUpdated(const QPoint &screenPos);
void touchResetSpeed();
void touchUpdateSpeed();
void touchDeaccelerate(int32 elapsed);
style::flatScroll _st;
ScrollBar hor, vert;
ScrollShadow topSh, bottomSh;
int32 _horValue, _vertValue;
bool _touchEnabled;
QTimer _touchTimer;
bool _touchScroll, _touchPress, _touchRightButton;
QPoint _touchStart, _touchPrevPos, _touchPos;
TouchScrollState _touchScrollState;
bool _touchPrevPosValid, _touchWaitingAcceleration;
QPoint _touchSpeed;
uint64 _touchSpeedTime, _touchAccelerationTime, _touchTime;
QTimer _touchScrollTimer;
bool _widgetAcceptsTouch;
};

View File

@@ -0,0 +1,178 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
namespace {
typedef QMap<QString, uint32> FontFamilyMap;
FontFamilyMap _fontFamilyMap;
}
namespace style {
FontData::FontData(uint32 size, uint32 flags, uint32 family, Font *other) : _size(size), _flags(flags), _family(family), f(_fontFamilies[family]), m(f) {
if (other) {
memcpy(modified, other, sizeof(modified));
} else {
memset(modified, 0, sizeof(modified));
}
modified[_flags] = Font(this);
f.setPixelSize(size);
f.setBold(_flags & FontBold);
f.setItalic(_flags & FontItalic);
f.setUnderline(_flags & FontUnderline);
f.setStyleStrategy(QFont::PreferQuality);
m = QFontMetrics(f);
height = m.height();
ascent = m.ascent();
descent = m.descent();
spacew = m.width(QLatin1Char(' '));
elidew = m.width(QLatin1Char('.')) * 3;
}
Font FontData::bold(bool set) const {
return otherFlagsFont(FontBold, set);
}
Font FontData::italic(bool set) const {
return otherFlagsFont(FontItalic, set);
}
Font FontData::underline(bool set) const {
return otherFlagsFont(FontUnderline, set);
}
uint32 FontData::flags() const {
return _flags;
}
Font FontData::otherFlagsFont(uint32 flag, bool set) const {
int32 newFlags = set ? (_flags | flag) : (_flags & ~flag);
if (!modified[newFlags].v()) {
modified[newFlags] = Font(_size, newFlags, _family, modified);
}
return modified[newFlags];
}
Font::Font(uint32 size, uint32 flags, const QString &family) {
if (_fontFamilyMap.isEmpty()) {
for (uint32 i = 0, s = style::_fontFamilies.size(); i != s; ++i) {
_fontFamilyMap.insert(style::_fontFamilies.at(i), i);
}
}
FontFamilyMap::const_iterator i = _fontFamilyMap.constFind(family);
if (i == _fontFamilyMap.cend()) {
style::_fontFamilies.push_back(family);
i = _fontFamilyMap.insert(family, style::_fontFamilies.size() - 1);
}
init(i.value(), size, flags, 0);
}
Font::Font(uint32 size, uint32 flags, uint32 family) {
init(size, flags, family, 0);
}
Font::Font(uint32 size, uint32 flags, uint32 family, Font *modified) {
init(size, flags, family, modified);
}
void Font::init(uint32 size, uint32 flags, uint32 family, Font *modified) {
uint32 key = _fontKey(size, flags, family);
FontDatas::const_iterator i = _fontsMap.constFind(key);
if (i == _fontsMap.cend()) {
i = _fontsMap.insert(key, new FontData(size, flags, family, modified));
}
ptr = i.value();
}
Color::Color(const Color &c) : ptr(c.owner ? new ColorData(*c.ptr) : c.ptr), owner(c.owner) {
}
Color::Color(const QColor &c) : owner(false) {
init(c.red(), c.green(), c.blue(), c.alpha());
}
Color::Color(uchar r, uchar g, uchar b, uchar a) : owner(false) {
init(r, g, b, a);
}
Color &Color::operator=(const Color &c) {
if (this != &c) {
if (owner) {
delete ptr;
}
ptr = c.owner ? new ColorData(*c.ptr) : c.ptr;
owner = c.owner;
}
return *this;
}
void Color::set(const QColor &newv) {
if (!owner) {
ptr = new ColorData(*ptr);
owner = true;
}
ptr->set(newv);
}
void Color::set(uchar r, uchar g, uchar b, uchar a) {
if (!owner) {
ptr = new ColorData(*ptr);
owner = true;
}
ptr->set(QColor(r, g, b, a));
}
void Color::init(uchar r, uchar g, uchar b, uchar a) {
uint32 key = _colorKey(r, g, b, a);
ColorDatas::const_iterator i = _colorsMap.constFind(key);
if (i == _colorsMap.cend()) {
i = _colorsMap.insert(key, new ColorData(r, g, b, a));
}
ptr = i.value();
}
Color::~Color() {
if (owner) {
delete ptr;
}
}
ColorData::ColorData(uchar r, uchar g, uchar b, uchar a) : c(int(r), int(g), int(b), int(a)), p(c), b(c) {
}
void ColorData::set(const QColor &color) {
c = color;
p = QPen(color);
b = QBrush(color);
}
void stopManager() {
for (FontDatas::const_iterator i = _fontsMap.cbegin(), e = _fontsMap.cend(); i != e; ++i) {
delete i.value();
}
_fontsMap.clear();
for (ColorDatas::const_iterator i = _colorsMap.cbegin(), e = _colorsMap.cend(); i != e; ++i) {
delete i.value();
}
_colorsMap.clear();
}
};

View File

@@ -0,0 +1,236 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/animation.h"
#include <QtCore/QString>
#include <QtGui/QColor>
#include <QtCore/QPoint>
#include <QtCore/QRect>
#include <QtGui/QCursor>
#include <QtGui/QFont>
namespace style {
class FontData;
class Font {
public:
Font(Qt::Initialization = Qt::Uninitialized) : ptr(0) {
}
Font(uint32 size, uint32 flags, const QString &family);
Font(uint32 size, uint32 flags = 0, uint32 family = 0);
Font &operator=(const Font &other) {
ptr = other.ptr;
return (*this);
}
FontData *operator->() const {
return ptr;
}
FontData *v() const {
return ptr;
}
operator bool() const {
return !!ptr;
}
private:
FontData *ptr;
void init(uint32 size, uint32 flags, uint32 family, Font *modified);
friend void startManager();
Font(FontData *p) : ptr(p) {
}
Font(uint32 size, uint32 flags, uint32 family, Font *modified);
friend class FontData;
};
enum FontFlagBits {
FontBoldBit,
FontItalicBit,
FontUnderlineBit,
FontFlagsBits
};
enum FontFlags {
FontBold = (1 << FontBoldBit),
FontItalic = (1 << FontItalicBit),
FontUnderline = (1 << FontUnderlineBit),
FontDifferentFlags = (1 << FontFlagsBits)
};
inline uint32 _fontKey(uint32 size, uint32 flags, uint32 family) {
return (((family << 10) | size) << FontFlagsBits) | flags;
}
class FontData {
public:
int32 width(const QString &str, int32 from, int32 to) {
return m.width(str.mid(from, to));
}
Font bold(bool set = true) const;
Font italic(bool set = true) const;
Font underline(bool set = true) const;
uint32 flags() const;
QFont f;
QFontMetrics m;
int32 height, ascent, descent, spacew, elidew;
private:
mutable Font modified[FontDifferentFlags];
Font otherFlagsFont(uint32 flag, bool set) const;
FontData(uint32 size, uint32 flags, uint32 family, Font *other);
friend class Font;
uint32 _size, _flags, _family;
};
inline bool operator==(const Font &a, const Font &b) {
return a.v() == b.v();
}
inline bool operator!=(const Font &a, const Font &b) {
return a.v() != b.v();
}
class ColorData;
class Color {
public:
Color(Qt::Initialization = Qt::Uninitialized) : ptr(0), owner(false) {
}
Color(const Color &c);
Color(const QColor &c);
Color(uchar r, uchar g, uchar b, uchar a = 255);
Color &operator=(const Color &c);
~Color();
void set(const QColor &newv);
void set(uchar r, uchar g, uchar b, uchar a = 255);
ColorData *operator->() const {
return ptr;
}
ColorData *v() const {
return ptr;
}
operator bool() const {
return !!ptr;
}
private:
ColorData *ptr;
bool owner;
void init(uchar r, uchar g, uchar b, uchar a);
friend void startManager();
Color(ColorData *p) : ptr(p) {
}
friend class ColorData;
};
inline uint32 _colorKey(uchar r, uchar g, uchar b, uchar a) {
return (((((uint32(r) << 8) | uint32(g)) << 8) | uint32(b)) << 8) | uint32(a);
}
class ColorData {
public:
QColor c;
QPen p;
QBrush b;
private:
ColorData(uchar r, uchar g, uchar b, uchar a);
void set(const QColor &c);
friend class Color;
};
inline bool operator==(const Color &a, const Color &b) {
return a->c == b->c;
}
inline bool operator!=(const Color &a, const Color &b) {
return a->c != b->c;
}
typedef QVector<QString> FontFamilies;
extern FontFamilies _fontFamilies;
typedef QMap<uint32, FontData*> FontDatas;
extern FontDatas _fontsMap;
typedef QMap<uint32, ColorData*> ColorDatas;
extern ColorDatas _colorsMap;
typedef float64 number;
typedef QString string;
typedef QRect rect;
typedef QPoint point;
typedef QSize size;
typedef anim::transition transition;
typedef Qt::CursorShape cursor;
static const cursor cur_default(Qt::ArrowCursor);
static const cursor cur_pointer(Qt::PointingHandCursor);
static const cursor cur_text(Qt::IBeamCursor);
static const cursor cur_cross(Qt::CrossCursor);
static const cursor cur_sizever(Qt::SizeVerCursor);
static const cursor cur_sizehor(Qt::SizeHorCursor);
static const cursor cur_sizebdiag(Qt::SizeBDiagCursor);
static const cursor cur_sizefdiag(Qt::SizeFDiagCursor);
static const cursor cur_sizeall(Qt::SizeAllCursor);
typedef Qt::Alignment align;
static const align al_topleft(Qt::AlignTop | Qt::AlignLeft);
static const align al_top(Qt::AlignTop | Qt::AlignHCenter);
static const align al_topright(Qt::AlignTop | Qt::AlignRight);
static const align al_right(Qt::AlignVCenter | Qt::AlignRight);
static const align al_bottomright(Qt::AlignBottom | Qt::AlignRight);
static const align al_bottom(Qt::AlignBottom | Qt::AlignHCenter);
static const align al_bottomleft(Qt::AlignBottom | Qt::AlignLeft);
static const align al_left(Qt::AlignVCenter | Qt::AlignLeft);
static const align al_center(Qt::AlignVCenter | Qt::AlignHCenter);
typedef QMargins margins;
typedef Font font;
typedef Color color;
void startManager();
void stopManager();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,435 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "gui/emoji_config.h"
#include "gui/style_core.h"
#include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
enum TextBlockType {
TextBlockNewline = 0x01,
TextBlockText = 0x02,
TextBlockEmoji = 0x03,
TextBlockSkip = 0x04,
};
enum TextBlockFlags {
TextBlockBold = 0x01,
TextBlockItalic = 0x02,
TextBlockUnderline = 0x04,
};
class ITextBlock {
public:
ITextBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))/*, _color(color)*/, _lpadding(0) {
if (length) {
if (str.at(_from + length - 1).unicode() == QChar::Space) {
_rpadding = font->spacew;
}
if (length > 1 && str.at(0).unicode() == QChar::Space) {
_lpadding = font->spacew;
}
}
}
uint16 from() const {
return _from;
}
int32 width() const {
return _width.toInt();
}
int32 lpadding() const {
return _lpadding.toInt();
}
int32 rpadding() const {
return _rpadding.toInt();
}
QFixed f_width() const {
return _width;
}
QFixed f_lpadding() const {
return _lpadding;
}
QFixed f_rpadding() const {
return _rpadding;
}
uint16 lnkIndex() const {
return (_flags >> 12) & 0xFFFF;
}
void setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
}
TextBlockType type() const {
return TextBlockType((_flags >> 8) & 0x0F);
}
int32 flags() const {
return (_flags & 0xFF);
}
const style::color &color() const {
static style::color tmp;
return tmp;//_color;
}
virtual ~ITextBlock() {
}
protected:
uint16 _from;
uint32 _flags; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
QFixed _width, _lpadding, _rpadding;
};
class NewlineBlock : public ITextBlock {
public:
Qt::LayoutDirection nextDirection() const {
return _nextDir;
}
private:
NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length) : ITextBlock(font, str, from, length, 0, st::transparent, 0), _nextDir(Qt::LayoutDirectionAuto) {
_flags |= ((TextBlockNewline & 0x0F) << 8);
}
Qt::LayoutDirection _nextDir;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
struct TextWord {
TextWord() {
}
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : from(from), width(width), rpadding(rpadding),
_rbearing(rbearing.value() > 0xFFFF ? 0xFFFF : (rbearing.value() < -0xFFFF ? -0xFFFF : rbearing.value())) {
}
QFixed f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
uint16 from;
int16 _rbearing;
QFixed width, rpadding;
};
class TextBlock : public ITextBlock {
public:
QFixed f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
private:
TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex);
typedef QVector<TextWord> TextWords;
TextWords _words;
friend class Text;
friend class TextParser;
friend class BlockParser;
friend class TextPainter;
};
class EmojiBlock : public ITextBlock {
public:
private:
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji);
const EmojiData *emoji;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
class SkipBlock : public ITextBlock {
public:
int32 height() const {
return _height;
}
private:
SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex);
int32 _height;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
class ITextLink {
public:
virtual void onClick(Qt::MouseButton) const = 0;
virtual const QString &text() const {
static const QString _tmp;
return _tmp;
}
virtual const QString &readable() const {
static const QString _tmp;
return _tmp;
}
virtual bool fullDisplayed() const {
return true;
}
virtual QString encoded() const {
return QString();
}
virtual ~ITextLink() {
}
};
typedef QSharedPointer<ITextLink> TextLinkPtr;
class TextLink : public ITextLink {
public:
TextLink(const QString &url, bool fullDisplayed = true) : _url(url), _fullDisplayed(fullDisplayed) {
QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString());
_readable = good.isValid() ? good.toDisplayString() : _url;
}
const QString &text() const {
return _url;
}
void onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
QDesktopServices::openUrl(TextLink::encoded());
}
}
const QString &readable() const {
return _readable;
}
bool fullDisplayed() const {
return _fullDisplayed;
}
QString encoded() const {
QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString());
QString result(good.isValid() ? good.toEncoded() : _url);
if (!QRegularExpression(qsl("^[a-zA-Z]+://")).match(result).hasMatch()) { // no protocol
return qsl("http://") + result;
}
return result;
}
private:
QString _url, _readable;
bool _fullDisplayed;
};
class EmailLink : public ITextLink {
public:
EmailLink(const QString &email) : _email(email) {
}
const QString &text() const {
return _email;
}
void onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
QDesktopServices::openUrl(qsl("mailto:") + _email);
}
}
const QString &readable() const {
return _email;
}
QString encoded() const {
return _email;
}
private:
QString _email;
};
static const QChar TextCommand(0x0010);
enum TextCommands {
TextCommandBold = 0x01,
TextCommandNoBold = 0x02,
TextCommandItalic = 0x03,
TextCommandNoItalic = 0x04,
TextCommandUnderline = 0x05,
TextCommandNoUnderline = 0x06,
TextCommandLinkIndex = 0x07, // 0 - NoLink
TextCommandLinkText = 0x08,
TextCommandColor = 0x09,
TextCommandNoColor = 0x0A,
TextCommandSkipBlock = 0x0B,
};
enum {
TextParseMultiline = 0x01,
TextParseLinks = 0x02,
TextParseRichText = 0x04,
};
struct TextParseOptions {
int32 flags;
int32 maxw;
int32 maxh;
Qt::LayoutDirection dir;
};
extern const TextParseOptions _defaultOptions;
extern const TextParseOptions _textPlainOptions;
enum TextSelectType {
TextSelectLetters = 0x01,
TextSelectWords = 0x02,
TextSelectParagraphs = 0x03,
};
typedef QPair<QString, QString> TextCustomTag; // open str and close str
typedef QMap<QChar, TextCustomTag> TextCustomTagsMap;
class Text {
public:
Text(int32 minResizeWidth = QFIXED_MAX);
Text(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false);
int32 countHeight(int32 width) const;
void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions);
void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap());
void setLink(uint16 lnkIndex, const TextLinkPtr &lnk);
bool hasLinks() const;
int32 maxWidth() const {
return _maxWidth.toInt();
}
int32 minHeight() const {
return _minHeight;
}
void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const;
void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1) const;
const TextLinkPtr &link(int32 x, int32 y, int32 width, style::align align = style::al_left) const;
void getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align = style::al_left) const;
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align = style::al_left) const;
uint32 adjustSelection(uint16 from, uint16 to, TextSelectType selectType) const;
QString original(uint16 selectedFrom = 0, uint16 selectedTo = 0xFFFF, bool expandLinks = true) const;
bool lastDots(uint32 dots, uint32 maxdots = 3) { // hack for typing animation
if (_text.size() < maxdots) return false;
int32 nowDots = 0, from = _text.size() - maxdots, to = _text.size();
for (int32 i = from; i < to; ++i) {
if (_text.at(i) == QChar('.')) {
++nowDots;
}
}
if (nowDots == dots) return false;
for (int32 j = from; j < from + dots; ++j) {
_text[j] = QChar('.');
}
for (int32 j = from + dots; j < to; ++j) {
_text[j] = QChar(' ');
}
return true;
}
void clean();
~Text() {
clean();
}
private:
QFixed _minResizeWidth, _maxWidth;
int32 _minHeight;
QString _text;
style::font _font;
typedef QVector<ITextBlock*> TextBlocks;
TextBlocks _blocks;
typedef QVector<TextLinkPtr> TextLinks;
TextLinks _links;
Qt::LayoutDirection _startDir;
friend class TextParser;
friend class TextPainter;
};
// text style
const style::textStyle *textstyleCurrent();
void textstyleSet(const style::textStyle *style);
inline void textstyleRestore() {
textstyleSet(0);
}
// text preprocess
QString textClean(const QString &text);
QString textRichPrepare(const QString &text);
QString textOneLine(const QString &text, bool trim = true, bool rich = false);
QString textAccentFold(const QString &text);
// textlnk
void textlnkOver(const TextLinkPtr &lnk);
const TextLinkPtr &textlnkOver();
void textlnkDown(const TextLinkPtr &lnk);
const TextLinkPtr &textlnkDown();
// textcmd
QString textcmdSkipBlock(ushort w, ushort h);
QString textcmdStartLink(ushort lnkIndex);
QString textcmdStartLink(const QString &url);
QString textcmdStopLink();
QString textcmdLink(ushort lnkIndex, const QString &text);
QString textcmdLink(const QString &url, const QString &text);
QString textcmdStartColor(const style::color &color);
QString textcmdStopColor();

View File

@@ -0,0 +1,18 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"

View File

@@ -0,0 +1,46 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
class TWidget : public QWidget {
Q_OBJECT
public:
TWidget(QWidget *parent = 0) : QWidget(parent) {
}
TWidget *tparent() {
return dynamic_cast<TWidget*>(parentWidget());
}
const TWidget *tparent() const {
return dynamic_cast<const TWidget*>(parentWidget());
}
virtual void leaveToChildEvent(QEvent *e) { // e -- from enterEvent() of child TWidget
}
protected:
void enterEvent(QEvent *e) {
TWidget *p(tparent());
if (p) p->leaveToChildEvent(e);
}
private:
};