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:
96
Telegram/SourceFiles/gui/animation.cpp
Normal file
96
Telegram/SourceFiles/gui/animation.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
292
Telegram/SourceFiles/gui/animation.h
Normal file
292
Telegram/SourceFiles/gui/animation.h
Normal 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 ¤t() 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 ¤t() 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;
|
||||
|
||||
};
|
74
Telegram/SourceFiles/gui/boxshadow.cpp
Normal file
74
Telegram/SourceFiles/gui/boxshadow.cpp
Normal 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());
|
||||
}
|
||||
}
|
40
Telegram/SourceFiles/gui/boxshadow.h
Normal file
40
Telegram/SourceFiles/gui/boxshadow.h
Normal 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;
|
||||
|
||||
};
|
113
Telegram/SourceFiles/gui/button.cpp
Normal file
113
Telegram/SourceFiles/gui/button.cpp
Normal 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;
|
||||
}
|
68
Telegram/SourceFiles/gui/button.h
Normal file
68
Telegram/SourceFiles/gui/button.h
Normal 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;
|
||||
|
||||
};
|
83
Telegram/SourceFiles/gui/countrycodeinput.cpp
Normal file
83
Telegram/SourceFiles/gui/countrycodeinput.cpp
Normal 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);
|
||||
}
|
||||
}
|
47
Telegram/SourceFiles/gui/countrycodeinput.h
Normal file
47
Telegram/SourceFiles/gui/countrycodeinput.h
Normal 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;
|
||||
|
||||
};
|
608
Telegram/SourceFiles/gui/countryinput.cpp
Normal file
608
Telegram/SourceFiles/gui/countryinput.cpp
Normal 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);
|
||||
}
|
||||
}
|
162
Telegram/SourceFiles/gui/countryinput.h
Normal file
162
Telegram/SourceFiles/gui/countryinput.h
Normal 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;
|
||||
|
||||
};
|
5567
Telegram/SourceFiles/gui/emoji_config.cpp
Normal file
5567
Telegram/SourceFiles/gui/emoji_config.cpp
Normal file
File diff suppressed because it is too large
Load Diff
82
Telegram/SourceFiles/gui/emoji_config.h
Normal file
82
Telegram/SourceFiles/gui/emoji_config.h
Normal 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);
|
168
Telegram/SourceFiles/gui/filedialog.cpp
Normal file
168
Telegram/SourceFiles/gui/filedialog.cpp
Normal 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;
|
||||
}
|
26
Telegram/SourceFiles/gui/filedialog.h
Normal file
26
Telegram/SourceFiles/gui/filedialog.h
Normal 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());
|
204
Telegram/SourceFiles/gui/flatbutton.cpp
Normal file
204
Telegram/SourceFiles/gui/flatbutton.cpp
Normal 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);
|
||||
}
|
||||
}
|
114
Telegram/SourceFiles/gui/flatbutton.h
Normal file
114
Telegram/SourceFiles/gui/flatbutton.h
Normal 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;
|
||||
};
|
211
Telegram/SourceFiles/gui/flatcheckbox.cpp
Normal file
211
Telegram/SourceFiles/gui/flatcheckbox.cpp
Normal 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);
|
||||
}
|
80
Telegram/SourceFiles/gui/flatcheckbox.h
Normal file
80
Telegram/SourceFiles/gui/flatcheckbox.h
Normal 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;
|
||||
|
||||
};
|
266
Telegram/SourceFiles/gui/flatinput.cpp
Normal file
266
Telegram/SourceFiles/gui/flatinput.cpp
Normal 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);
|
||||
}
|
87
Telegram/SourceFiles/gui/flatinput.h
Normal file
87
Telegram/SourceFiles/gui/flatinput.h
Normal 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;
|
||||
};
|
122
Telegram/SourceFiles/gui/flatlabel.cpp
Normal file
122
Telegram/SourceFiles/gui/flatlabel.cpp
Normal 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();
|
||||
}
|
54
Telegram/SourceFiles/gui/flatlabel.h
Normal file
54
Telegram/SourceFiles/gui/flatlabel.h
Normal 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;
|
||||
|
||||
};
|
483
Telegram/SourceFiles/gui/flattextarea.cpp
Normal file
483
Telegram/SourceFiles/gui/flattextarea.cpp
Normal 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);
|
||||
}
|
94
Telegram/SourceFiles/gui/flattextarea.h
Normal file
94
Telegram/SourceFiles/gui/flattextarea.h
Normal 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;
|
||||
};
|
295
Telegram/SourceFiles/gui/images.cpp
Normal file
295
Telegram/SourceFiles/gui/images.cpp
Normal 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();
|
||||
}
|
168
Telegram/SourceFiles/gui/images.h
Normal file
168
Telegram/SourceFiles/gui/images.h
Normal 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();
|
96
Telegram/SourceFiles/gui/phoneinput.cpp
Normal file
96
Telegram/SourceFiles/gui/phoneinput.cpp
Normal 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();
|
||||
}
|
||||
}
|
54
Telegram/SourceFiles/gui/phoneinput.h
Normal file
54
Telegram/SourceFiles/gui/phoneinput.h
Normal 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);
|
||||
|
||||
};
|
597
Telegram/SourceFiles/gui/scrollarea.cpp
Normal file
597
Telegram/SourceFiles/gui/scrollarea.cpp
Normal 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) {
|
||||
}
|
174
Telegram/SourceFiles/gui/scrollarea.h
Normal file
174
Telegram/SourceFiles/gui/scrollarea.h
Normal 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;
|
||||
|
||||
};
|
178
Telegram/SourceFiles/gui/style_core.cpp
Normal file
178
Telegram/SourceFiles/gui/style_core.cpp
Normal 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();
|
||||
}
|
||||
|
||||
};
|
236
Telegram/SourceFiles/gui/style_core.h
Normal file
236
Telegram/SourceFiles/gui/style_core.h
Normal 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();
|
||||
|
||||
};
|
4043
Telegram/SourceFiles/gui/text.cpp
Normal file
4043
Telegram/SourceFiles/gui/text.cpp
Normal file
File diff suppressed because it is too large
Load Diff
435
Telegram/SourceFiles/gui/text.h
Normal file
435
Telegram/SourceFiles/gui/text.h
Normal 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();
|
18
Telegram/SourceFiles/gui/twidget.cpp
Normal file
18
Telegram/SourceFiles/gui/twidget.cpp
Normal 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"
|
46
Telegram/SourceFiles/gui/twidget.h
Normal file
46
Telegram/SourceFiles/gui/twidget.h
Normal 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:
|
||||
|
||||
};
|
Reference in New Issue
Block a user