fixed text selection

This commit is contained in:
fourtf 2018-01-16 00:26:04 +01:00
parent 1133b33318
commit a190eda075
20 changed files with 278 additions and 1052 deletions

View file

@ -258,14 +258,6 @@ win32 {
-ldwmapi \
-lgdi32
# SOURCES += platform/borderless/qwinwidget.cpp \
# platform/borderless/winnativewindow.cpp \
# platform/borderless/widget.cpp
# HEADERS += platform/borderless/qwinwidget.h \
# platform/borderless/winnativewindow.h \
# platform/borderless/widget.h
DEFINES += "USEWINSDK"
}
}

View file

@ -160,7 +160,7 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
this->bufferValid = false;
}
if (!this->bufferValid) {
if (!this->bufferValid || !selection.isEmpty()) {
this->updateBuffer(pixmap, messageIndex, selection);
}

View file

@ -29,6 +29,7 @@ int MessageLayoutContainer::getHeight() const
void MessageLayoutContainer::clear()
{
this->elements.clear();
this->lines.clear();
this->height = 0;
this->line = 0;
@ -36,6 +37,7 @@ void MessageLayoutContainer::clear()
this->currentY = 0;
this->lineStart = 0;
this->lineHeight = 0;
this->charIndex = 0;
}
void MessageLayoutContainer::addElement(MessageLayoutElement *element)
@ -106,7 +108,16 @@ void MessageLayoutContainer::breakLine()
element->getRect().y() + this->lineHeight + yExtra));
}
this->lines.push_back({(int)lineStart, QRect(0, this->currentY, this->width, lineHeight)});
if (this->lines.size() != 0) {
this->lines.back().endIndex = this->lineStart;
this->lines.back().endCharIndex = this->charIndex;
}
this->lines.push_back({(int)lineStart, 0, this->charIndex, 0,
QRect(-100000, this->currentY, 200000, lineHeight)});
for (int i = this->lineStart; i < this->elements.size(); i++) {
this->charIndex += this->elements[i]->getSelectionIndexCount();
}
this->lineStart = this->elements.size();
this->currentX = 0;
@ -132,8 +143,10 @@ void MessageLayoutContainer::finish()
}
if (this->lines.size() != 0) {
this->lines[0].rect.setTop(0);
this->lines.back().rect.setBottom(this->height);
this->lines[0].rect.setTop(-100000);
this->lines.back().rect.setBottom(100000);
this->lines.back().endIndex = this->elements.size();
this->lines.back().endCharIndex = this->charIndex;
}
}
@ -166,6 +179,166 @@ void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, int yOffse
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
Selection &selection)
{
// don't draw anything
if (selection.min.messageIndex > messageIndex || selection.max.messageIndex < messageIndex) {
return;
}
// fully selected
if (selection.min.messageIndex < messageIndex && selection.max.messageIndex > messageIndex) {
for (Line &line : this->lines) {
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(this->elements[line.startIndex]->getRect().left());
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
painter.fillRect(rect, QColor(255, 255, 0, 127));
}
return;
}
int lineIndex = 0;
int index = 0;
// start in this message
if (selection.min.messageIndex == messageIndex) {
for (; lineIndex < this->lines.size(); lineIndex++) {
Line &line = this->lines[lineIndex];
index = line.startCharIndex;
bool returnAfter = false;
bool breakAfter = false;
int x = this->elements[line.startIndex]->getRect().left();
int r = this->elements[line.endIndex - 1]->getRect().right();
if (line.endCharIndex < selection.min.charIndex) {
continue;
}
for (int i = line.startIndex; i < line.endIndex; i++) {
int c = this->elements[i]->getSelectionIndexCount();
if (index + c > selection.min.charIndex) {
x = this->elements[i]->getXFromIndex(selection.min.charIndex - index);
// ends in same line
if (selection.max.messageIndex == messageIndex &&
line.endCharIndex > /*=*/selection.max.charIndex) //
{
returnAfter = true;
index = line.startCharIndex;
for (int i = line.startIndex; i < line.endIndex; i++) {
int c = this->elements[i]->getSelectionIndexCount();
if (index + c > selection.max.charIndex) {
r = this->elements[i]->getXFromIndex(selection.max.charIndex -
index);
break;
}
index += c;
}
}
// ends in same line end
if (selection.max.messageIndex != messageIndex) {
int lineIndex2 = lineIndex + 1;
for (; lineIndex2 < this->lines.size(); lineIndex2++) {
Line &line = this->lines[lineIndex2];
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(this->elements[line.startIndex]->getRect().left());
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
painter.fillRect(rect, QColor(255, 255, 0, 127));
}
returnAfter = true;
} else {
lineIndex++;
breakAfter = true;
}
break;
}
index += c;
}
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(x);
rect.setRight(r);
painter.fillRect(rect, QColor(255, 255, 0, 127));
if (returnAfter) {
return;
}
if (breakAfter) {
break;
}
}
}
// start in this message
for (; lineIndex < this->lines.size(); lineIndex++) {
Line &line = this->lines[lineIndex];
index = line.startCharIndex;
bool breakAfter = false;
// just draw the garbage
if (line.endCharIndex < /*=*/selection.max.charIndex) {
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(this->elements[line.startIndex]->getRect().left());
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
painter.fillRect(rect, QColor(255, 255, 0, 127));
continue;
}
int r = this->elements[line.endIndex - 1]->getRect().right();
for (int i = line.startIndex; i < line.endIndex; i++) {
int c = this->elements[i]->getSelectionIndexCount();
if (index + c > selection.max.charIndex) {
r = this->elements[i]->getXFromIndex(selection.max.charIndex - index);
break;
}
index += c;
}
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(this->elements[line.startIndex]->getRect().left());
rect.setRight(r);
painter.fillRect(rect, QColor(255, 255, 0, 127));
break;
}
}
void MessageLayoutContainer::_paintLine(QPainter &painter, Line &line, int x, int y)
{
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()));
rect.setBottom(std::min(this->height, rect.bottom()));
rect.setLeft(this->elements[line.startIndex]->getRect().left());
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
painter.fillRect(rect, QColor(255, 255, 0, 127));
}
// selection
@ -183,10 +356,10 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
}
}
assert(line != this->lines.end());
int lineStart = line->startIndex;
int lineStart = line == this->lines.end() ? this->lines.back().startIndex : line->startIndex;
if (line != this->lines.end()) {
line++;
}
int lineEnd = line == this->lines.end() ? this->elements.size() : line->startIndex;
int index = 0;
@ -204,10 +377,12 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
}
// this is the word
if (point.x() > this->elements[i]->getRect().left()) {
if (point.x() < this->elements[i]->getRect().right()) {
index += this->elements[i]->getMouseOverIndex(point);
break;
}
index += this->elements[i]->getSelectionIndexCount();
}
qDebug() << index;

View file

@ -6,11 +6,12 @@
#include <QPoint>
#include <QRect>
#include "messages/selection.hpp"
class QPainter;
namespace chatterino {
namespace messages {
class Selection;
namespace layouts {
class MessageLayoutElement;
@ -72,23 +73,28 @@ public:
int getSelectionIndex(QPoint point);
private:
struct Line {
int startIndex;
int endIndex;
int startCharIndex;
int endCharIndex;
QRect rect;
};
// helpers
void _addElement(MessageLayoutElement *element);
void _paintLine(QPainter &painter, Line &line, int x, int y);
// variables
int line;
int height;
int currentX, currentY;
int charIndex = 0;
size_t lineStart = 0;
int lineHeight = 0;
int spaceWidth = 4;
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
struct Line {
int startIndex;
QRect rect;
};
std::vector<Line> lines;
};
} // namespace layouts

View file

@ -1,6 +1,7 @@
#include "messages/layouts/messagelayoutelement.hpp"
#include "messages/messageelement.hpp"
#include <QDebug>
#include <QPainter>
namespace chatterino {
@ -88,6 +89,18 @@ int ImageLayoutElement::getMouseOverIndex(const QPoint &abs)
return 0;
}
int ImageLayoutElement::getXFromIndex(int index)
{
if (index <= 0) {
return this->getRect().left();
} else if (index == 1) {
// fourtf: remove space width
return this->getRect().right();
} else {
return this->getRect().right();
}
}
//
// TEXT
//
@ -127,7 +140,44 @@ void TextLayoutElement::paintAnimated(QPainter &, int)
int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
{
if (abs.x() < this->getRect().left()) {
return 0;
}
QFontMetrics &metrics =
singletons::FontManager::getInstance().getFontMetrics(this->style, this->scale);
int x = this->getRect().left();
for (int i = 0; i < this->text.size(); i++) {
int w = metrics.width(this->text[i]);
if (x + w > abs.x()) {
return i;
}
x += w;
}
return this->getSelectionIndexCount();
}
int TextLayoutElement::getXFromIndex(int index)
{
QFontMetrics &metrics =
singletons::FontManager::getInstance().getFontMetrics(this->style, this->scale);
if (index <= 0) {
return this->getRect().left();
} else if (index < this->text.size()) {
int x = 0;
for (int i = 0; i < index; i++) {
x += metrics.width(this->text[i]);
}
return x + this->getRect().left();
} else {
return this->getRect().right();
}
}
} // namespace layouts
} // namespace messages

View file

@ -36,6 +36,7 @@ public:
virtual void paint(QPainter &painter) = 0;
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
virtual int getMouseOverIndex(const QPoint &abs) = 0;
virtual int getXFromIndex(int index) = 0;
protected:
bool trailingSpace = true;
@ -58,6 +59,7 @@ protected:
virtual void paint(QPainter &painter) override;
virtual void paintAnimated(QPainter &painter, int yOffset) override;
virtual int getMouseOverIndex(const QPoint &abs) override;
virtual int getXFromIndex(int index) override;
private:
Image &image;
@ -76,6 +78,7 @@ protected:
virtual void paint(QPainter &painter) override;
virtual void paintAnimated(QPainter &painter, int yOffset) override;
virtual int getMouseOverIndex(const QPoint &abs) override;
virtual int getXFromIndex(int index) override;
private:
QString text;

View file

@ -11,7 +11,7 @@
#include <QTime>
#include <boost/noncopyable.hpp>
#include <util/emotemap.hpp>
#include "util/emotemap.hpp"
namespace chatterino {
class Channel;

View file

@ -8,24 +8,36 @@ struct SelectionItem {
SelectionItem()
{
messageIndex = charIndex = 0;
this->messageIndex = 0;
this->charIndex = 0;
}
SelectionItem(int _messageIndex, int _charIndex)
{
this->messageIndex = _messageIndex;
this->charIndex = _charIndex;
}
bool isSmallerThan(const SelectionItem &other) const
bool operator<(const SelectionItem &b) const
{
return this->messageIndex < other.messageIndex ||
(this->messageIndex == other.messageIndex && this->charIndex < other.charIndex);
if (this->messageIndex < b.messageIndex) {
return true;
}
if (this->messageIndex == b.messageIndex && this->charIndex < b.charIndex) {
return true;
}
return false;
}
bool equals(const SelectionItem &other) const
bool operator>(const SelectionItem &b) const
{
return this->messageIndex == other.messageIndex && this->charIndex == other.charIndex;
return b.operator<(*this);
}
bool operator==(const SelectionItem &b) const
{
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
}
};
@ -45,14 +57,14 @@ struct Selection {
, min(start)
, max(end)
{
if (max.isSmallerThan(min)) {
if (min > max) {
std::swap(this->min, this->max);
}
}
bool isEmpty() const
{
return this->start.equals(this->end);
return this->start == this->end;
}
bool isSingleMessage() const
@ -60,5 +72,5 @@ struct Selection {
return this->min.messageIndex == this->max.messageIndex;
}
};
}
}
} // namespace messages
} // namespace chatterino

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Ian Bannerman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,555 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* */
/* */
/* File is originally from
* https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate/src */
/* */
/* It has been modified to support borderless window (HTTRANSPARENT) & to remove
* pre Qt5 cruft */
/* */
/* */
#include "QWinWidget.h"
#include <qt_windows.h>
#include <QApplication>
#include <QEvent>
#include <QFocusEvent>
#include <QWindow>
/*!
\class QWinWidget qwinwidget.h
\brief The QWinWidget class is a Qt widget that can be child of a
native Win32 widget.
The QWinWidget class is the bridge between an existing application
user interface developed using native Win32 APIs or toolkits like
MFC, and Qt based GUI elements.
Using QWinWidget as the parent of QDialogs will ensure that
modality, placement and stacking works properly throughout the
entire application. If the child widget is a top level window that
uses the \c WDestructiveClose flag, QWinWidget will destroy itself
when the child window closes down.
Applications moving to Qt can use QWinWidget to add new
functionality, and gradually replace the existing interface.
*/
QWinWidget::QWinWidget()
: QWidget(nullptr)
, m_Layout()
, p_Widget(nullptr)
, m_ParentNativeWindowHandle(nullptr)
, _prevFocus(nullptr)
, _reenableParent(false)
{
// Create a native window and give it geometry values * devicePixelRatio for
// HiDPI support
p_ParentWinNativeWindow = new WinNativeWindow(
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio(),
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio());
// If you want to set a minimize size for your app, do so here
// p_ParentWinNativeWindow->setMinimumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// If you want to set a maximum size for your app, do so here
// p_ParentWinNativeWindow->setMaximumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// Save the native window handle for shorthand use
m_ParentNativeWindowHandle = p_ParentWinNativeWindow->hWnd;
Q_ASSERT(m_ParentNativeWindowHandle);
// Create the child window & embed it into the native one
if (m_ParentNativeWindowHandle) {
SetWindowLong((HWND)winId(), GWL_STYLE,
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
QWindow *window = windowHandle();
window->setProperty("_q_embedded_native_parent_handle",
(WId)m_ParentNativeWindowHandle);
SetParent((HWND)winId(), m_ParentNativeWindowHandle);
window->setFlags(Qt::FramelessWindowHint);
QEvent e(QEvent::EmbeddingControl);
QApplication::sendEvent(this, &e);
}
// Pass along our window handle & widget pointer to WinFramelessWidget so we
// can exchange messages
p_ParentWinNativeWindow->childWindow = (HWND)winId();
p_ParentWinNativeWindow->childWidget = this;
// Clear margins & spacing & add the layout to prepare for the MainAppWidget
setContentsMargins(0, 0, 0, 0);
setLayout(&m_Layout);
m_Layout.setContentsMargins(0, 0, 0, 0);
m_Layout.setSpacing(0);
// Create the true app widget
// p_Widget = new Widget(this);
// m_Layout.addWidget(p_Widget);
// p_Widget->setParent(this, Qt::Widget);
// p_Widget->setVisible(true);
// Update the BORDERWIDTH value if needed for HiDPI displays
BORDERWIDTH = BORDERWIDTH * window()->devicePixelRatio();
// Update the TOOLBARHEIGHT value to match the height of toolBar * if
// needed, the HiDPI display
// if (p_Widget->toolBar) {
// TOOLBARHEIGHT =
// p_Widget->toolBar->height() * window()->devicePixelRatio();
// }
// You need to keep the native window in sync with the Qt window & children,
// so wire min/max/close buttons to
// slots inside of QWinWidget. QWinWidget can then talk with the native
// window as needed
// if (p_Widget->minimizeButton) {
// connect(p_Widget->minimizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMinimizeButtonClicked);
// }
// if (p_Widget->maximizeButton) {
// connect(p_Widget->maximizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMaximizeButtonClicked);
// }
// if (p_Widget->closeButton) {
// connect(p_Widget->closeButton, &QPushButton::clicked, this,
// &QWinWidget::onCloseButtonClicked);
// }
// Send the parent native window a WM_SIZE message to update the widget size
SendMessage(m_ParentNativeWindowHandle, WM_SIZE, 0, 0);
}
/*!
Destroys this object, freeing all allocated resources.
*/
QWinWidget::~QWinWidget()
{
}
/*!
Returns the handle of the native Win32 parent window.
*/
HWND
QWinWidget::getParentWindow() const
{
return m_ParentNativeWindowHandle;
}
/*!
\reimp
*/
void
QWinWidget::childEvent(QChildEvent *e)
{
QObject *obj = e->child();
if (obj->isWidgetType()) {
if (e->added()) {
if (obj->isWidgetType()) {
obj->installEventFilter(this);
}
} else if (e->removed() && _reenableParent) {
_reenableParent = false;
EnableWindow(m_ParentNativeWindowHandle, true);
obj->removeEventFilter(this);
}
}
QWidget::childEvent(e);
}
/*! \internal */
void
QWinWidget::saveFocus()
{
if (!_prevFocus)
_prevFocus = ::GetFocus();
if (!_prevFocus)
_prevFocus = getParentWindow();
}
/*!
Shows this widget. Overrides QWidget::show().
\sa showCentered()
*/
void
QWinWidget::show()
{
ShowWindow(m_ParentNativeWindowHandle, true);
saveFocus();
QWidget::show();
}
/*!
Centers this widget over the native parent window. Use this
function to have Qt toplevel windows (i.e. dialogs) positioned
correctly over their native parent windows.
\code
QWinWidget qwin(hParent);
qwin.center();
QMessageBox::information(&qwin, "Caption", "Information Text");
\endcode
This will center the message box over the client area of hParent.
*/
void
QWinWidget::center()
{
const QWidget *child = findChild<QWidget *>();
if (child && !child->isWindow()) {
qWarning("QWinWidget::center: Call this function only for QWinWidgets "
"with toplevel children");
}
RECT r;
GetWindowRect(m_ParentNativeWindowHandle, &r);
setGeometry((r.right - r.left) / 2 + r.left, (r.bottom - r.top) / 2 + r.top,
0, 0);
}
/*!
\obsolete
Call center() instead.
*/
void
QWinWidget::showCentered()
{
center();
show();
}
void
QWinWidget::setGeometry(int x, int y, int w, int h)
{
p_ParentWinNativeWindow->setGeometry(
x * window()->devicePixelRatio(), y * window()->devicePixelRatio(),
w * window()->devicePixelRatio(), h * window()->devicePixelRatio());
}
/*!
Sets the focus to the window that had the focus before this widget
was shown, or if there was no previous window, sets the focus to
the parent window.
*/
void
QWinWidget::resetFocus()
{
if (_prevFocus)
::SetFocus(_prevFocus);
else
::SetFocus(getParentWindow());
}
// Tell the parent native window to minimize
void
QWinWidget::onMinimizeButtonClicked()
{
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
// Tell the parent native window to maximize or restore as appropriate
void
QWinWidget::onMaximizeButtonClicked()
{
if (p_Widget->maximizeButton->isChecked()) {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
} else {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_RESTORE, 0);
}
}
void
QWinWidget::onCloseButtonClicked()
{
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
// Do nothing, and thus, don't actually close the window
}
}
bool
QWinWidget::nativeEvent(const QByteArray &, void *message, long *result)
{
MSG *msg = (MSG *)message;
if (msg->message == WM_SETFOCUS) {
Qt::FocusReason reason;
if (::GetKeyState(VK_LBUTTON) < 0 || ::GetKeyState(VK_RBUTTON) < 0)
reason = Qt::MouseFocusReason;
else if (::GetKeyState(VK_SHIFT) < 0)
reason = Qt::BacktabFocusReason;
else
reason = Qt::TabFocusReason;
QFocusEvent e(QEvent::FocusIn, reason);
QApplication::sendEvent(this, &e);
}
// Only close if safeToClose clears()
if (msg->message == WM_CLOSE) {
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
*result = 0; // Set the message to 0 to ignore it, and thus, don't
// actually close
return true;
}
}
// Double check WM_SIZE messages to see if the parent native window is
// maximized
if (msg->message == WM_SIZE) {
if (p_Widget && p_Widget->maximizeButton) {
// Get the window state
WINDOWPLACEMENT wp;
GetWindowPlacement(m_ParentNativeWindowHandle, &wp);
// If we're maximized,
if (wp.showCmd == SW_MAXIMIZE) {
// Maximize button should show as Restore
p_Widget->maximizeButton->setChecked(true);
} else {
// Maximize button should show as Maximize
p_Widget->maximizeButton->setChecked(false);
}
}
}
// Pass NCHITTESTS on the window edges as determined by BORDERWIDTH &
// TOOLBARHEIGHT through to the parent native window
if (msg->message == WM_NCHITTEST) {
RECT WindowRect;
int x, y;
GetWindowRect(msg->hwnd, &WindowRect);
x = GET_X_LPARAM(msg->lParam) - WindowRect.left;
y = GET_Y_LPARAM(msg->lParam) - WindowRect.top;
if (x >= BORDERWIDTH &&
x <= WindowRect.right - WindowRect.left - BORDERWIDTH &&
y >= BORDERWIDTH && y <= TOOLBARHEIGHT) {
if (false) { // if (p_Widget->toolBar) {
// If the mouse is over top of the toolbar area BUT is actually
// positioned over a child widget of the toolbar,
// Then we don't want to enable dragging. This allows for
// buttons in the toolbar, eg, a Maximize button, to keep the
// mouse interaction
if (QApplication::widgetAt(QCursor::pos()) != p_Widget->toolBar)
return false;
} else {
// The mouse is over the toolbar area & is NOT over a child of
// the toolbar, so pass this message
// through to the native window for HTCAPTION dragging
*result = HTTRANSPARENT;
return true;
}
} else if (x < BORDERWIDTH && y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
}
return false;
}
return false;
}
/*!
\reimp
*/
bool
QWinWidget::eventFilter(QObject *o, QEvent *e)
{
QWidget *w = (QWidget *)o;
switch (e->type()) {
case QEvent::WindowDeactivate:
if (w->isModal() && w->isHidden())
BringWindowToTop(m_ParentNativeWindowHandle);
break;
case QEvent::Hide:
if (_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, true);
_reenableParent = false;
}
resetFocus();
if (w->testAttribute(Qt::WA_DeleteOnClose) && w->isWindow())
deleteLater();
break;
case QEvent::Show:
if (w->isWindow()) {
saveFocus();
hide();
if (w->isModal() && !_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, false);
_reenableParent = true;
}
}
break;
case QEvent::Close: {
::SetActiveWindow(m_ParentNativeWindowHandle);
if (w->testAttribute(Qt::WA_DeleteOnClose))
deleteLater();
break;
}
default:
break;
}
return QWidget::eventFilter(o, e);
}
/*! \reimp
*/
void
QWinWidget::focusInEvent(QFocusEvent *e)
{
QWidget *candidate = this;
switch (e->reason()) {
case Qt::TabFocusReason:
case Qt::BacktabFocusReason:
while (!(candidate->focusPolicy() & Qt::TabFocus)) {
candidate = candidate->nextInFocusChain();
if (candidate == this) {
candidate = 0;
break;
}
}
if (candidate) {
candidate->setFocus(e->reason());
if (e->reason() == Qt::BacktabFocusReason ||
e->reason() == Qt::TabFocusReason) {
candidate->setAttribute(Qt::WA_KeyboardFocusChange);
candidate->window()->setAttribute(
Qt::WA_KeyboardFocusChange);
}
if (e->reason() == Qt::BacktabFocusReason)
QWidget::focusNextPrevChild(false);
}
break;
default:
break;
}
}
/*! \reimp
*/
bool
QWinWidget::focusNextPrevChild(bool next)
{
QWidget *curFocus = focusWidget();
if (!next) {
if (!curFocus->isWindow()) {
QWidget *nextFocus = curFocus->nextInFocusChain();
QWidget *prevFocus = 0;
QWidget *topLevel = 0;
while (nextFocus != curFocus) {
if (nextFocus->focusPolicy() & Qt::TabFocus) {
prevFocus = nextFocus;
topLevel = 0;
}
nextFocus = nextFocus->nextInFocusChain();
}
if (!topLevel) {
return QWidget::focusNextPrevChild(false);
}
}
} else {
QWidget *nextFocus = curFocus;
while (1 && nextFocus != 0) {
nextFocus = nextFocus->nextInFocusChain();
if (nextFocus->focusPolicy() & Qt::TabFocus) {
return QWidget::focusNextPrevChild(true);
}
}
}
::SetFocus(m_ParentNativeWindowHandle);
return true;
}

View file

@ -1,109 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* */
/* File is originally from
* https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate/src */
/* */
/* It has been modified to support borderless window (HTTTRANSPARENT) & to
* remove pre Qt5 cruft */
/* */
/* */
// Declaration of the QWinWidget classes
#ifndef QWINWIDGET_H
#define QWINWIDGET_H
#include <QVBoxLayout>
#include <QWidget>
#include "WinNativeWindow.h"
#include "widget.h"
class QWinWidget : public QWidget
{
Q_OBJECT
public:
QWinWidget();
~QWinWidget();
void show();
void center();
void showCentered();
void setGeometry(int x, int y, int w, int h);
HWND getParentWindow() const;
public slots:
void onMaximizeButtonClicked();
void onMinimizeButtonClicked();
void onCloseButtonClicked();
protected:
void childEvent(QChildEvent *e) override;
bool eventFilter(QObject *o, QEvent *e) override;
bool focusNextPrevChild(bool next) override;
void focusInEvent(QFocusEvent *e) override;
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
private:
QVBoxLayout m_Layout;
Widget *p_Widget;
WinNativeWindow *p_ParentWinNativeWindow;
HWND m_ParentNativeWindowHandle;
HWND _prevFocus;
bool _reenableParent;
int BORDERWIDTH = 6; // Adjust this as you wish for # of pixels on the
// edges to show resize handles
int TOOLBARHEIGHT = 40; // Adjust this as you wish for # of pixels from the
// top to allow dragging the window
void saveFocus();
void resetFocus();
};
#endif // QWINWIDGET_H

View file

@ -1,11 +0,0 @@
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Set a black background for funsies
QPalette Pal(palette());
Pal.setColor(QPalette::Background, Qt::blue);
setAutoFillBackground(true);
setPalette(Pal);
}

View file

@ -1,28 +0,0 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <QPushButton>
#include <QToolBar>
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
// If you want to have Max/Min/Close buttons, look at how QWinWidget uses these
QPushButton *maximizeButton = nullptr;
QPushButton *minimizeButton = nullptr;
QPushButton *closeButton = nullptr;
// If you want to enable dragging the window when the mouse is over top of, say, a QToolBar,
// then look at how QWinWidget uses this
QToolBar *toolBar = nullptr;
signals:
public slots:
};
#endif // WIDGET_H

View file

@ -1,235 +0,0 @@
#include "WinNativeWindow.h"
#include <dwmapi.h>
#include <stdexcept>
HWND WinNativeWindow::childWindow = nullptr;
QWidget *WinNativeWindow::childWidget = nullptr;
WinNativeWindow::WinNativeWindow(const int x, const int y, const int width, const int height)
: hWnd(nullptr)
{
// The native window technically has a background color. You can set it here
HBRUSH windowBackground = CreateSolidBrush(RGB(255, 255, 255));
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX wcx = {0};
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW | CS_DROPSHADOW;
wcx.hInstance = hInstance;
wcx.lpfnWndProc = WndProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.lpszClassName = L"WindowClass";
wcx.hbrBackground = windowBackground;
wcx.hCursor = LoadCursor(hInstance, IDC_ARROW);
RegisterClassEx(&wcx);
if (FAILED(RegisterClassEx(&wcx))) {
throw std::runtime_error("Couldn't register window class");
}
// Create a native window with the appropriate style
hWnd = CreateWindow(L"WindowClass", L"WindowTitle", aero_borderless, x, y, width, height, 0, 0,
hInstance, nullptr);
if (!hWnd) {
throw std::runtime_error("couldn't create window because of reasons");
}
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
// This code may be required for aero shadows on some versions of Windows
// const MARGINS aero_shadow_on = { 1, 1, 1, 1 };
// DwmExtendFrameIntoClientArea(hWnd, &aero_shadow_on);
SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
}
WinNativeWindow::~WinNativeWindow()
{
// Hide the window & send the destroy message
ShowWindow(hWnd, SW_HIDE);
DestroyWindow(hWnd);
}
LRESULT CALLBACK WinNativeWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WinNativeWindow *window =
reinterpret_cast<WinNativeWindow *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (!window) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
switch (message) {
// ALT + SPACE or F10 system menu
case WM_SYSCOMMAND: {
if (wParam == SC_KEYMENU) {
RECT winrect;
GetWindowRect(hWnd, &winrect);
TrackPopupMenu(GetSystemMenu(hWnd, false), TPM_TOPALIGN | TPM_LEFTALIGN,
winrect.left + 5, winrect.top + 5, 0, hWnd, NULL);
break;
} else {
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
case WM_NCCALCSIZE: {
// this kills the window frame and title bar we added with
// WS_THICKFRAME and WS_CAPTION
return 0;
}
// If the parent window gets any close messages, send them over to
// QWinWidget and don't actually close here
case WM_CLOSE: {
if (childWindow) {
SendMessage(childWindow, WM_CLOSE, 0, 0);
return 0;
}
break;
}
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
case WM_NCHITTEST: {
const LONG borderWidth =
8 * childWidget->window()->devicePixelRatio(); // This value can be arbitrarily
// large as only
// intentionally-HTTRANSPARENT'd
// messages arrive here
RECT winrect;
GetWindowRect(hWnd, &winrect);
long x = GET_X_LPARAM(lParam);
long y = GET_Y_LPARAM(lParam);
// bottom left corner
if (x >= winrect.left && x < winrect.left + borderWidth && y < winrect.bottom &&
y >= winrect.bottom - borderWidth) {
return HTBOTTOMLEFT;
}
// bottom right corner
if (x < winrect.right && x >= winrect.right - borderWidth && y < winrect.bottom &&
y >= winrect.bottom - borderWidth) {
return HTBOTTOMRIGHT;
}
// top left corner
if (x >= winrect.left && x < winrect.left + borderWidth && y >= winrect.top &&
y < winrect.top + borderWidth) {
return HTTOPLEFT;
}
// top right corner
if (x < winrect.right && x >= winrect.right - borderWidth && y >= winrect.top &&
y < winrect.top + borderWidth) {
return HTTOPRIGHT;
}
// left border
if (x >= winrect.left && x < winrect.left + borderWidth) {
return HTLEFT;
}
// right border
if (x < winrect.right && x >= winrect.right - borderWidth) {
return HTRIGHT;
}
// bottom border
if (y < winrect.bottom && y >= winrect.bottom - borderWidth) {
return HTBOTTOM;
}
// top border
if (y >= winrect.top && y < winrect.top + borderWidth) {
return HTTOP;
}
// If it wasn't a border but we still got the message, return
// HTCAPTION to allow click-dragging the window
return HTCAPTION;
break;
}
// When this native window changes size, it needs to manually resize the
// QWinWidget child
case WM_SIZE: {
RECT winrect;
GetClientRect(hWnd, &winrect);
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hWnd, &wp);
if (childWidget) {
if (wp.showCmd == SW_MAXIMIZE) {
childWidget->setGeometry(
8, 8 // Maximized window draw 8 pixels off screen
,
winrect.right / childWidget->window()->devicePixelRatio() - 16,
winrect.bottom / childWidget->window()->devicePixelRatio() - 16);
} else {
childWidget->setGeometry(
0, 0, winrect.right / childWidget->window()->devicePixelRatio(),
winrect.bottom / childWidget->window()->devicePixelRatio());
}
}
break;
}
case WM_GETMINMAXINFO: {
MINMAXINFO *minMaxInfo = (MINMAXINFO *)lParam;
if (window->minimumSize.required) {
minMaxInfo->ptMinTrackSize.x = window->getMinimumWidth();
;
minMaxInfo->ptMinTrackSize.y = window->getMinimumHeight();
}
if (window->maximumSize.required) {
minMaxInfo->ptMaxTrackSize.x = window->getMaximumWidth();
minMaxInfo->ptMaxTrackSize.y = window->getMaximumHeight();
}
return 0;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
void WinNativeWindow::setGeometry(const int x, const int y, const int width, const int height)
{
MoveWindow(hWnd, x, y, width, height, 1);
}
void WinNativeWindow::setMinimumSize(const int width, const int height)
{
this->minimumSize.required = true;
this->minimumSize.width = width;
this->minimumSize.height = height;
}
int WinNativeWindow::getMinimumWidth()
{
return minimumSize.width;
}
int WinNativeWindow::getMinimumHeight()
{
return minimumSize.height;
}
void WinNativeWindow::setMaximumSize(const int width, const int height)
{
this->maximumSize.required = true;
this->maximumSize.width = width;
this->maximumSize.height = height;
}
int WinNativeWindow::getMaximumWidth()
{
return maximumSize.width;
}
int WinNativeWindow::getMaximumHeight()
{
return maximumSize.height;
}

View file

@ -1,53 +0,0 @@
#ifndef WINNATIVEWINDOW_H
#define WINNATIVEWINDOW_H
#include "Windows.h"
#include "Windowsx.h"
#include <QWidget>
class WinNativeWindow
{
public:
WinNativeWindow(const int x, const int y, const int width, const int height);
~WinNativeWindow();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// These six functions exist to restrict native window resizing to whatever
// you want your app minimum/maximum size to be
void setMinimumSize(const int width, const int height);
int getMinimumHeight();
int getMinimumWidth();
void setMaximumSize(const int width, const int height);
int getMaximumHeight();
int getMaximumWidth();
void setGeometry(const int x, const int y, const int width, const int height);
HWND hWnd;
static HWND childWindow;
static QWidget *childWidget;
private:
struct sizeType {
sizeType()
: required(false)
, width(0)
, height(0)
{
}
bool required;
int width;
int height;
};
sizeType minimumSize;
sizeType maximumSize;
DWORD aero_borderless = WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
WS_THICKFRAME | WS_CLIPCHILDREN;
};
#endif // WINNATIVEWINDOW_H

View file

@ -4,7 +4,7 @@
#include <QColor>
#include <boost/signals2.hpp>
#include <pajlada/settings/setting.hpp>
#include <util/serialize-custom.hpp>
#include "util/serialize-custom.hpp"
namespace chatterino {
namespace singletons {

View file

@ -1,6 +1,6 @@
#include "aboutpage.hpp"
#include <util/layoutcreator.hpp>
#include "util/layoutcreator.hpp"
#include <QLabel>
#include <QVBoxLayout>

View file

@ -7,7 +7,7 @@
#include <QPushButton>
#include <QVBoxLayout>
#include <util/layoutcreator.hpp>
#include "util/layoutcreator.hpp"
#define THEME_ITEMS "White", "Light", "Dark", "Black"

View file

@ -3,8 +3,8 @@
#include <QLabel>
#include <QTextEdit>
#include <util/layoutcreator.hpp>
#include "singletons/commandmanager.hpp"
#include "util/layoutcreator.hpp"
// clang-format off
#define TEXT "One command per line.\n"\

View file

@ -1,6 +1,6 @@
#include "emotespage.hpp"
#include <util/layoutcreator.hpp>
#include "util/layoutcreator.hpp"
namespace chatterino {
namespace widgets {