chore(release): 1.0.0~beta2
Building GXDE Arch Package / detect (push) Skipped
Building GXDE Arch Package / aarch64-arch (push) Skipped
Building GXDE Arch Package / x86_64-arch (push) Skipped
Building GXDE Testing Package / call-arch (push) Skipped
Building Program / call (push) Skipped
Building GXDE Testing Package / call-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-i386-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-amd64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-arm64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-riscv64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-loong64-zhuangzhuang (push) Skipped
Building GXDE Package / ${{ inputs.display-name || 'Build Job' }} (push) Skipped
Building GXDE Arch Package / detect (push) Skipped
Building GXDE Arch Package / aarch64-arch (push) Skipped
Building GXDE Arch Package / x86_64-arch (push) Skipped
Building GXDE Testing Package / call-arch (push) Skipped
Building Program / call (push) Skipped
Building GXDE Testing Package / call-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-i386-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-amd64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-arm64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-riscv64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-loong64-zhuangzhuang (push) Skipped
Building GXDE Package / ${{ inputs.display-name || 'Build Job' }} (push) Skipped
- 会话列表拖拽排序/多选/批量删除 - 128k/1024k token 上下文上限 - 待办取消置顶;全屏隐藏联动 - 英文回退翻译补齐;新应用图标 - DTK 关于窗口、分词/朗读对话框、消息常驻操作
This commit is contained in:
+22
@@ -117,6 +117,20 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
|
||||
}
|
||||
});
|
||||
|
||||
connect(portal, &PortalClient::fullscreenChanged, this, [this](bool fullscreen) {
|
||||
if (!Config::instance()->getBool(QStringLiteral("panel/hideOnFullscreen"), false)) {
|
||||
return;
|
||||
}
|
||||
if (fullscreen) {
|
||||
if (m_panel && m_panel->isVisible()) {
|
||||
m_panel->dismiss();
|
||||
}
|
||||
} else if (m_panelPinned || !m_model->isEmpty()) {
|
||||
ensurePanel();
|
||||
m_panel->popup();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_model, &StagingModel::countChanged, this, [this]() {
|
||||
if (!m_model->isEmpty() && m_panel) {
|
||||
m_panel->popup();
|
||||
@@ -194,6 +208,14 @@ void App::showTodoWindow()
|
||||
{
|
||||
if (!m_todoWindow) {
|
||||
m_todoWindow = new TodoWindow(TodoModel::instance());
|
||||
connect(m_todoWindow, &TodoWindow::recreateRequested, this, [this]() {
|
||||
if (m_todoWindow) {
|
||||
m_todoWindow->close();
|
||||
m_todoWindow->deleteLater();
|
||||
m_todoWindow = nullptr;
|
||||
}
|
||||
showTodoWindow();
|
||||
});
|
||||
}
|
||||
m_todoWindow->popup();
|
||||
}
|
||||
|
||||
+165
-4
@@ -75,11 +75,20 @@ public:
|
||||
: QListWidget(parent)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
setDragEnabled(true);
|
||||
setDragDropMode(QAbstractItemView::DragDrop);
|
||||
setDefaultDropAction(Qt::MoveAction);
|
||||
setDropIndicatorShown(true);
|
||||
setDragDropOverwriteMode(false);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
}
|
||||
|
||||
signals:
|
||||
void droppedAt(int index, const QMimeData *mime);
|
||||
void left();
|
||||
void reordered();
|
||||
void deleteRequested(const QList<int> &rows);
|
||||
void renameRequested(int row);
|
||||
|
||||
protected:
|
||||
void leaveEvent(QEvent *event) override
|
||||
@@ -88,10 +97,9 @@ protected:
|
||||
QListWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override
|
||||
{
|
||||
if (mimeOk(event->mimeData())) {
|
||||
if (event->source() == this || mimeOk(event->mimeData())) {
|
||||
event->acceptProposedAction();
|
||||
} else {
|
||||
event->ignore();
|
||||
@@ -100,13 +108,27 @@ protected:
|
||||
|
||||
void dragMoveEvent(QDragMoveEvent *event) override
|
||||
{
|
||||
if (mimeOk(event->mimeData())) {
|
||||
if (event->source() == this || mimeOk(event->mimeData())) {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
void dropEvent(QDropEvent *event) override
|
||||
{
|
||||
if (event->source() == this) {
|
||||
const QModelIndex dropIndex = indexAt(event->position().toPoint());
|
||||
const int to = dropIndex.isValid() ? dropIndex.row() : count();
|
||||
const int from = currentRow();
|
||||
if (from >= 0 && to >= 0 && to != from && to != from + 1) {
|
||||
QListWidgetItem *item = takeItem(from);
|
||||
const int target = to > from ? to - 1 : to;
|
||||
insertItem(target, item);
|
||||
setCurrentRow(target);
|
||||
Q_EMIT reordered();
|
||||
}
|
||||
event->acceptProposedAction();
|
||||
return;
|
||||
}
|
||||
if (!mimeOk(event->mimeData())) {
|
||||
event->ignore();
|
||||
return;
|
||||
@@ -115,6 +137,37 @@ protected:
|
||||
Q_EMIT droppedAt(index.isValid() ? index.row() : -1, event->mimeData());
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void contextMenuEvent(QContextMenuEvent *event) override
|
||||
{
|
||||
const QModelIndex index = indexAt(event->pos());
|
||||
QMenu menu(this);
|
||||
QAction *rename = nullptr;
|
||||
if (index.isValid()) {
|
||||
rename = menu.addAction(tr("重命名对话"));
|
||||
}
|
||||
QAction *remove = menu.addAction(tr("删除选中"));
|
||||
QAction *chosen = menu.exec(event->globalPos());
|
||||
if (!chosen) {
|
||||
return;
|
||||
}
|
||||
if (chosen == rename) {
|
||||
Q_EMIT renameRequested(index.row());
|
||||
return;
|
||||
}
|
||||
if (chosen == remove) {
|
||||
QList<int> rows;
|
||||
for (const QModelIndex &selected : selectionModel()->selectedRows()) {
|
||||
rows.append(selected.row());
|
||||
}
|
||||
if (rows.isEmpty() && index.isValid()) {
|
||||
rows.append(index.row());
|
||||
}
|
||||
if (!rows.isEmpty()) {
|
||||
Q_EMIT deleteRequested(rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ChatView : public QTextBrowser
|
||||
@@ -546,6 +599,12 @@ void ChatWindow::setupUi()
|
||||
connect(m_sidebar, &QListWidget::currentRowChanged, this, &ChatWindow::selectConversation);
|
||||
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::droppedAt, this,
|
||||
&ChatWindow::handleDropOnConversation);
|
||||
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::reordered, this,
|
||||
&ChatWindow::reorderConversations);
|
||||
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::deleteRequested, this,
|
||||
&ChatWindow::deleteConversations);
|
||||
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::renameRequested, this,
|
||||
&ChatWindow::renameConversation);
|
||||
connect(m_messages, &ChatMessageList::dropped, this, &ChatWindow::handleDroppedMime);
|
||||
connect(m_messages, &ChatMessageList::linkActivated, this, &ChatWindow::handleAnchor);
|
||||
connect(m_messages, &ChatMessageList::copyRequested, this, [this](int index) {
|
||||
@@ -681,12 +740,83 @@ void ChatWindow::selectConversation(int index)
|
||||
renderConversation();
|
||||
}
|
||||
|
||||
void ChatWindow::reorderConversations()
|
||||
{
|
||||
if (m_conversations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QHash<QString, ChatConversation> byId;
|
||||
for (const ChatConversation &conversation : m_conversations) {
|
||||
byId.insert(conversation.id, conversation);
|
||||
}
|
||||
QVector<ChatConversation> ordered;
|
||||
for (int i = 0; i < m_sidebar->count(); ++i) {
|
||||
const QString id = m_sidebar->item(i)->data(Qt::UserRole).toString();
|
||||
auto it = byId.find(id);
|
||||
if (it != byId.end()) {
|
||||
ordered.append(it.value());
|
||||
byId.erase(it);
|
||||
}
|
||||
}
|
||||
for (auto it = byId.begin(); it != byId.end(); ++it) {
|
||||
ordered.append(it.value());
|
||||
}
|
||||
m_conversations = ordered;
|
||||
const int row = m_sidebar->currentRow();
|
||||
if (row >= 0 && row < m_conversations.size()) {
|
||||
m_current = row;
|
||||
}
|
||||
save();
|
||||
updateSidebar();
|
||||
}
|
||||
|
||||
void ChatWindow::deleteConversations(const QList<int> &rows)
|
||||
{
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_client->busy()) {
|
||||
m_client->cancel();
|
||||
m_streamingIndex = -1;
|
||||
}
|
||||
QList<int> sorted = rows;
|
||||
std::sort(sorted.begin(), sorted.end(), std::greater<int>());
|
||||
for (int row : sorted) {
|
||||
if (row >= 0 && row < m_conversations.size()) {
|
||||
m_conversations.removeAt(row);
|
||||
}
|
||||
}
|
||||
m_current = m_conversations.isEmpty() ? -1
|
||||
: qBound(0, m_current, m_conversations.size() - 1);
|
||||
save();
|
||||
updateSidebar();
|
||||
renderConversation();
|
||||
}
|
||||
|
||||
void ChatWindow::renameConversation(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_conversations.size()) {
|
||||
return;
|
||||
}
|
||||
bool ok = false;
|
||||
const QString title = QInputDialog::getText(nullptr, tr("重命名对话"), tr("名称"),
|
||||
QLineEdit::Normal,
|
||||
m_conversations.at(row).title, &ok);
|
||||
if (ok && !title.trimmed().isEmpty()) {
|
||||
m_conversations[row].title = title.trimmed();
|
||||
save();
|
||||
updateSidebar();
|
||||
renderConversation();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWindow::updateSidebar()
|
||||
{
|
||||
const QSignalBlocker blocker(m_sidebar);
|
||||
m_sidebar->clear();
|
||||
for (const ChatConversation &conversation : m_conversations) {
|
||||
auto *item = new QListWidgetItem(conversation.title, m_sidebar);
|
||||
item->setData(Qt::UserRole, conversation.id);
|
||||
QString preview = tr("新对话");
|
||||
for (int i = conversation.messages.size() - 1; i >= 0; --i) {
|
||||
const ChatMessage &message = conversation.messages.at(i);
|
||||
@@ -874,8 +1004,26 @@ bool ChatWindow::needsCompression() const
|
||||
if (!Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true)) {
|
||||
return false;
|
||||
}
|
||||
const QVector<ChatMessage> &messages = m_conversations.at(m_current).messages;
|
||||
|
||||
const int keep = Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40);
|
||||
return m_conversations.at(m_current).messages.size() > keep + 4;
|
||||
if (messages.size() > keep + 4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* also honour a token budget (128k / 1024k); a rough estimate is enough */
|
||||
const qint64 tokenLimit =
|
||||
Config::instance()->getInt(QStringLiteral("chat/contextTokens"), 128000);
|
||||
if (tokenLimit <= 0) {
|
||||
return false;
|
||||
}
|
||||
qint64 tokens = 0;
|
||||
for (const ChatMessage &message : messages) {
|
||||
for (const QChar &ch : message.content) {
|
||||
tokens += ch.unicode() < 128 ? 1 : 2;
|
||||
}
|
||||
}
|
||||
return tokens / 4 > tokenLimit;
|
||||
}
|
||||
|
||||
void ChatWindow::beginCompression(bool thenSend)
|
||||
@@ -1153,6 +1301,17 @@ void ChatWindow::openSettings()
|
||||
Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40));
|
||||
form->addRow(tr("上下文条数"), contextSpin);
|
||||
|
||||
auto *tokenBox = new QComboBox(&dialog);
|
||||
tokenBox->addItem(tr("128k"), 128000);
|
||||
tokenBox->addItem(tr("1024k"), 1024000);
|
||||
const int storedTokens =
|
||||
Config::instance()->getInt(QStringLiteral("chat/contextTokens"), 128000);
|
||||
const int tokenIndex = tokenBox->findData(storedTokens);
|
||||
if (tokenIndex >= 0) {
|
||||
tokenBox->setCurrentIndex(tokenIndex);
|
||||
}
|
||||
form->addRow(tr("上下文长度"), tokenBox);
|
||||
|
||||
auto *autoCompressBox = new QCheckBox(tr("超出后自动压缩旧消息"), &dialog);
|
||||
autoCompressBox->setChecked(
|
||||
Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true));
|
||||
@@ -1201,6 +1360,8 @@ void ChatWindow::openSettings()
|
||||
Config::instance()->set(QStringLiteral("chat/contextLength"), contextSpin->value());
|
||||
Config::instance()->set(QStringLiteral("chat/autoCompress"),
|
||||
autoCompressBox->isChecked());
|
||||
Config::instance()->set(QStringLiteral("chat/contextTokens"),
|
||||
tokenBox->currentData().toInt());
|
||||
const QJsonDocument mcpDocument =
|
||||
QJsonDocument::fromJson(mcpEdit->toPlainText().toUtf8());
|
||||
if (mcpDocument.isArray() || mcpEdit->toPlainText().trimmed().isEmpty()) {
|
||||
|
||||
@@ -62,6 +62,9 @@ private:
|
||||
void renderConversation();
|
||||
void updateHeader();
|
||||
void updateSidebar();
|
||||
void reorderConversations();
|
||||
void deleteConversations(const QList<int> &rows);
|
||||
void renameConversation(int row);
|
||||
void sendMessage();
|
||||
void requestCompletion();
|
||||
void runToolCalls(const QVector<ToolCall> &calls, int index, const QJsonArray &results);
|
||||
|
||||
+16
-8
@@ -19,7 +19,7 @@ int main(int argc, char *argv[])
|
||||
app.setOrganizationName(QStringLiteral("GXDE"));
|
||||
app.setApplicationName(QStringLiteral("gxde-ai-see"));
|
||||
app.setApplicationDisplayName(QStringLiteral("GXDE 小埃同学"));
|
||||
app.setApplicationVersion(QStringLiteral("1.0.0~beta"));
|
||||
app.setApplicationVersion(QStringLiteral("1.0.0~beta2"));
|
||||
QIcon appIcon = QIcon::fromTheme(QStringLiteral("gxde-ai-see"));
|
||||
if (appIcon.isNull()) {
|
||||
appIcon = QIcon(QStringLiteral("/usr/share/icons/hicolor/256x256/apps/gxde-ai-see.png"));
|
||||
@@ -35,17 +35,25 @@ int main(int argc, char *argv[])
|
||||
QStringLiteral("/usr/share/gxde-ai-see/translations"),
|
||||
QStringLiteral("/usr/local/share/gxde-ai-see/translations"),
|
||||
};
|
||||
bool translated = false;
|
||||
QStringList candidates{ locale, locale.section(QLatin1Char('_'), 0, 0) };
|
||||
/* ensure unsupported locales fall back to English, never to the Chinese
|
||||
* source strings */
|
||||
if (!locale.startsWith(QLatin1String("zh"))) {
|
||||
candidates.append(QStringLiteral("en"));
|
||||
}
|
||||
for (const QString &dir : translationDirs) {
|
||||
if (translator->load(QStringLiteral("gxde-ai-see_") + locale, dir) ||
|
||||
translator->load(QStringLiteral("gxde-ai-see_") + locale.section(QLatin1Char('_'), 0, 0),
|
||||
dir)) {
|
||||
app.installTranslator(translator);
|
||||
translated = true;
|
||||
bool loaded = false;
|
||||
for (const QString &candidate : candidates) {
|
||||
if (translator->load(QStringLiteral("gxde-ai-see_") + candidate, dir)) {
|
||||
app.installTranslator(translator);
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (loaded) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Q_UNUSED(translated)
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("GXDE 小埃同学"));
|
||||
|
||||
@@ -53,6 +53,9 @@ PortalClient::PortalClient(QObject *parent)
|
||||
bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath),
|
||||
QString::fromLatin1(kInterface), QStringLiteral("PortalClosed"), this,
|
||||
SLOT(onPortalClosed()));
|
||||
bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath),
|
||||
QString::fromLatin1(kInterface), QStringLiteral("FullscreenChanged"), this,
|
||||
SLOT(onFullscreenChanged(bool)));
|
||||
}
|
||||
|
||||
bool PortalClient::beginPortal(const QRect &panelRect)
|
||||
@@ -96,6 +99,11 @@ void PortalClient::onDragStarted(const QString &mimeTypes)
|
||||
Q_EMIT dragStarted(mimeTypes);
|
||||
}
|
||||
|
||||
void PortalClient::onFullscreenChanged(bool fullscreen)
|
||||
{
|
||||
Q_EMIT fullscreenChanged(fullscreen);
|
||||
}
|
||||
|
||||
void PortalClient::onPortalClosed()
|
||||
{
|
||||
Q_EMIT portalClosed();
|
||||
|
||||
@@ -34,12 +34,14 @@ signals:
|
||||
void dragStarted(const QString &mimeTypes);
|
||||
void dragEnded();
|
||||
void portalClosed();
|
||||
void fullscreenChanged(bool fullscreen);
|
||||
void dropped(const QString &uuid, bool accepted);
|
||||
|
||||
private slots:
|
||||
void onDragStarted(const QString &mimeTypes);
|
||||
void onDragEnded();
|
||||
void onPortalClosed();
|
||||
void onFullscreenChanged(bool fullscreen);
|
||||
void onDropped(const QString &uuid, bool accepted);
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QMenu>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
@@ -159,6 +162,7 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent)
|
||||
|
||||
m_rightMargin = Config::instance()->getInt(QStringLiteral("todo/rightMargin"), 16);
|
||||
m_topMargin = Config::instance()->getInt(QStringLiteral("todo/topMargin"), 72);
|
||||
m_keepAbove = Config::instance()->getBool(QStringLiteral("todo/keepAbove"), true);
|
||||
|
||||
setupWindow();
|
||||
setupUi();
|
||||
@@ -172,6 +176,11 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent)
|
||||
|
||||
void TodoWindow::setupWindow()
|
||||
{
|
||||
if (!m_keepAbove) {
|
||||
/* plain toplevel: shows in the taskbar and can be covered/moved */
|
||||
setWindowFlag(Qt::Tool, false);
|
||||
return;
|
||||
}
|
||||
#ifdef GXDE_HAVE_LAYERSHELLQT
|
||||
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
|
||||
createWinId();
|
||||
@@ -308,6 +317,9 @@ void TodoWindow::addEntry()
|
||||
|
||||
void TodoWindow::applyMargins()
|
||||
{
|
||||
if (!m_keepAbove) {
|
||||
return;
|
||||
}
|
||||
#ifdef GXDE_HAVE_LAYERSHELLQT
|
||||
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
|
||||
if (auto *layer = LayerShellQt::Window::get(windowHandle())) {
|
||||
@@ -322,6 +334,26 @@ void TodoWindow::applyMargins()
|
||||
}
|
||||
}
|
||||
|
||||
void TodoWindow::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
QMenu menu(this);
|
||||
QAction *pin = menu.addAction(tr("置顶"));
|
||||
pin->setCheckable(true);
|
||||
pin->setChecked(m_keepAbove);
|
||||
QAction *hideAction = menu.addAction(tr("隐藏"));
|
||||
QAction *chosen = menu.exec(event->globalPos());
|
||||
if (chosen == hideAction) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
if (chosen == pin) {
|
||||
const bool keep = pin->isChecked();
|
||||
Config::instance()->set(QStringLiteral("todo/keepAbove"), keep);
|
||||
Config::instance()->sync();
|
||||
Q_EMIT recreateRequested();
|
||||
}
|
||||
}
|
||||
|
||||
void TodoWindow::saveMargins()
|
||||
{
|
||||
Config::instance()->set(QStringLiteral("todo/rightMargin"), m_rightMargin);
|
||||
@@ -332,6 +364,13 @@ void TodoWindow::saveMargins()
|
||||
void TodoWindow::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton && event->position().y() < 48) {
|
||||
if (!m_keepAbove) {
|
||||
if (windowHandle()) {
|
||||
windowHandle()->startSystemMove();
|
||||
}
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
m_dragging = true;
|
||||
m_dragStart = event->position().toPoint();
|
||||
m_dragStartRight = m_rightMargin;
|
||||
|
||||
@@ -28,11 +28,15 @@ public:
|
||||
void refreshLayout();
|
||||
|
||||
protected:
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
signals:
|
||||
void recreateRequested();
|
||||
|
||||
private:
|
||||
void setupWindow();
|
||||
void setupUi();
|
||||
@@ -45,6 +49,7 @@ private:
|
||||
QLabel *m_countLabel = nullptr;
|
||||
int m_rightMargin = 16;
|
||||
int m_topMargin = 72;
|
||||
bool m_keepAbove = true;
|
||||
bool m_dragging = false;
|
||||
QPoint m_dragStart;
|
||||
int m_dragStartRight = 0;
|
||||
|
||||
Reference in New Issue
Block a user