FreeCAD

Форк
0
/
DockWindowManager.cpp 
649 строк · 22.0 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2007 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25
#ifndef _PreComp_
26
# include <array>
27
# include <QAction>
28
# include <QApplication>
29
# include <QDockWidget>
30
# include <QMap>
31
# include <QMouseEvent>
32
# include <QPointer>
33
# include <QTimer>
34
#endif
35

36
#include <boost/algorithm/string/predicate.hpp>
37

38
#include <App/Application.h>
39
#include <Base/Tools.h>
40

41
#include "DockWindowManager.h"
42
#include "MainWindow.h"
43
#include "OverlayManager.h"
44

45

46
using namespace Gui;
47

48
DockWindowItems::DockWindowItems() = default;
49

50
DockWindowItems::~DockWindowItems() = default;
51

52
void DockWindowItems::addDockWidget(const char* name, Qt::DockWidgetArea pos, bool visibility, bool tabbed)
53
{
54
    DockWindowItem item;
55
    item.name = QString::fromUtf8(name);
56
    item.pos = pos;
57
    item.visibility = visibility;
58
    item.tabbed = tabbed;
59
    _items << item;
60
}
61

62
void DockWindowItems::setDockingArea(const char* name, Qt::DockWidgetArea pos)
63
{
64
    for (QList<DockWindowItem>::iterator it = _items.begin(); it != _items.end(); ++it) {
65
        if (it->name == QString::fromUtf8(name)) {
66
            it->pos = pos;
67
            break;
68
        }
69
    }
70
}
71

72
void DockWindowItems::setVisibility(const char* name, bool v)
73
{
74
    for (QList<DockWindowItem>::iterator it = _items.begin(); it != _items.end(); ++it) {
75
        if (it->name == QString::fromUtf8(name)) {
76
            it->visibility = v;
77
            break;
78
        }
79
    }
80
}
81

82
void DockWindowItems::setVisibility(bool v)
83
{
84
    for (QList<DockWindowItem>::iterator it = _items.begin(); it != _items.end(); ++it) {
85
        it->visibility = v;
86
    }
87
}
88

89
const QList<DockWindowItem>& DockWindowItems::dockWidgets() const
90
{
91
    return this->_items;
92
}
93

94
// -----------------------------------------------------------
95

96
namespace Gui {
97

98
class DockWidgetEventFilter: public QObject {
99
public:
100
    bool eventFilter(QObject *o, QEvent *e) {
101
        if (!o->isWidgetType() || e->type() != QEvent::MouseMove)
102
            return false;
103
        auto widget = qobject_cast<QDockWidget*>(o);
104
        if (!widget || !widget->isFloating()) {
105
            if (overridden) {
106
                overridden = false;
107
                QApplication::restoreOverrideCursor();
108
            }
109
            return false;
110
        }
111
        if (static_cast<QMouseEvent*>(e)->buttons() != Qt::NoButton)
112
            return false;
113
        auto pos = QCursor::pos();
114
        QPoint topLeft = widget->mapToGlobal(QPoint(cursorMargin, cursorMargin));
115
        int h = widget->frameGeometry().height();
116
        int w = widget->frameGeometry().width();
117
        QPoint bottomRight = widget->mapToGlobal(QPoint(w-cursorMargin, h-cursorMargin));
118
        bool left = QRect(topLeft - QPoint(cursorMargin,cursorMargin), QSize(cursorMargin, h)).contains(pos);
119
        bool right = QRect(bottomRight.x(), topLeft.y(), cursorMargin, h).contains(pos);
120
        bool bottom = QRect(topLeft.x()-cursorMargin, bottomRight.y(), w, cursorMargin).contains(pos);
121
        auto cursor = Qt::ArrowCursor;
122
        if (left && bottom)
123
            cursor = Qt::SizeBDiagCursor;
124
        else if (right && bottom)
125
            cursor = Qt::SizeFDiagCursor;
126
        else if (bottom)
127
            cursor = Qt::SizeVerCursor;
128
        else if (left || right)
129
            cursor = Qt::SizeHorCursor;
130
        else if (overridden) {
131
            overridden = false;
132
            QApplication::restoreOverrideCursor();
133
            return false;
134
        }
135
        if (overridden)
136
            QApplication::changeOverrideCursor(cursor);
137
        else {
138
            overridden = true;
139
            QApplication::setOverrideCursor(cursor);
140
        }
141
        return false;
142
    }
143

144
    bool overridden = false;
145
    int cursorMargin = 5;
146
};
147

148
struct DockWindowManagerP
149
{
150
    QList<QDockWidget*> _dockedWindows;
151
    QMap<QString, QPointer<QWidget> > _dockWindows;
152
    DockWindowItems _dockWindowItems;
153
    ParameterGrp::handle _hPref;
154
    boost::signals2::scoped_connection _connParam;
155
    QTimer _timer;
156
    DockWidgetEventFilter _dockWidgetEventFilter;
157
    QPointer<OverlayManager> overlayManager;
158
};
159
} // namespace Gui
160

161
DockWindowManager* DockWindowManager::_instance = nullptr;
162

163
DockWindowManager* DockWindowManager::instance()
164
{
165
    if (!_instance)
166
        _instance = new DockWindowManager;
167
    return _instance;
168
}
169

170
void DockWindowManager::destruct()
171
{
172
    delete _instance;
173
    _instance = nullptr;
174
}
175

176
DockWindowManager::DockWindowManager()
177
{
178
    d = new DockWindowManagerP;
179
    d->_hPref = App::GetApplication().GetUserParameter().GetGroup("BaseApp/MainWindow/DockWindows");
180

181
    auto grp = App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/DockWindows");
182
    if (grp->GetBool("ActivateOverlay", true)) {
183
        setupOverlayManagement();
184
    }
185
}
186

187
DockWindowManager::~DockWindowManager()
188
{
189
    d->_dockedWindows.clear();
190
    delete d;
191
}
192

193
bool DockWindowManager::isOverlayActivated() const
194
{
195
    return (d->overlayManager != nullptr);
196
}
197

198
void DockWindowManager::setupOverlayManagement()
199
{
200
    d->overlayManager = OverlayManager::instance();
201

202
    qApp->installEventFilter(&d->_dockWidgetEventFilter);
203

204
    d->_dockWidgetEventFilter.cursorMargin = d->_hPref->GetInt("CursorMargin", 5);
205
    d->_connParam = d->_hPref->Manager()->signalParamChanged.connect(
206
        [this](ParameterGrp *Param, ParameterGrp::ParamType Type, const char *name, const char *) {
207
            if(Param == d->_hPref) {
208
                switch(Type) {
209
                case ParameterGrp::ParamType::FCBool:
210
                    // For batch process UI setting changes, e.g. loading new preferences
211
                    d->_timer.start(100);
212
                    break;
213
                case ParameterGrp::ParamType::FCInt:
214
                    if (name && boost::equals(name, "CursorMargin"))
215
                        d->_dockWidgetEventFilter.cursorMargin = d->_hPref->GetInt("CursorMargin", 5);
216
                    break;
217
                default:
218
                    break;
219
                }
220
            }
221
        });
222

223
    d->_timer.setSingleShot(true);
224

225
    connect(&d->_timer, &QTimer::timeout, [this](){
226
        for(auto w : this->getDockWindows()) {
227
            if (auto dw = qobject_cast<QDockWidget*>(w)) {
228
                QSignalBlocker blocker(dw);
229
                QByteArray dockName = dw->toggleViewAction()->data().toByteArray();
230
                dw->setVisible(d->_hPref->GetBool(dockName, dw->isVisible()));
231
            }
232
        }
233
    });
234
}
235

236
/**
237
 * Adds a new dock window to the main window and embeds the given \a widget.
238
 */
239
QDockWidget* DockWindowManager::addDockWindow(const char* name, QWidget* widget, Qt::DockWidgetArea pos)
240
{
241
    if(!widget)
242
        return nullptr;
243
    QDockWidget *dw = qobject_cast<QDockWidget*>(widget->parentWidget());
244
    if(dw)
245
        return dw;
246

247
    // creates the dock widget as container to embed this widget
248
    MainWindow* mw = getMainWindow();
249
    dw = new QDockWidget(mw);
250

251
    if (d->overlayManager) {
252
        d->overlayManager->setupTitleBar(dw);
253
    }
254

255
    // Note: By default all dock widgets are hidden but the user can show them manually in the view menu.
256
    // First, hide immediately the dock widget to avoid flickering, after setting up the dock widgets
257
    // MainWindow::loadLayoutSettings() is called to restore the layout.
258
    dw->hide();
259
    switch (pos) {
260
    case Qt::LeftDockWidgetArea:
261
    case Qt::RightDockWidgetArea:
262
    case Qt::TopDockWidgetArea:
263
    case Qt::BottomDockWidgetArea:
264
        mw->addDockWidget(pos, dw);
265
    default:
266
        break;
267
    }
268
    connect(dw, &QObject::destroyed,
269
            this, &DockWindowManager::onDockWidgetDestroyed);
270
    connect(widget, &QObject::destroyed,
271
            this, &DockWindowManager::onWidgetDestroyed);
272

273
    // add the widget to the dock widget
274
    widget->setParent(dw);
275
    dw->setWidget(widget);
276

277
    // set object name and window title needed for i18n stuff
278
    dw->setObjectName(QString::fromUtf8(name));
279
    QString title = widget->windowTitle();
280
    if (title.isEmpty())
281
        title = QDockWidget::tr(name);
282
    dw->setWindowTitle(title);
283
    dw->setFeatures(QDockWidget::DockWidgetClosable
284
                    | QDockWidget::DockWidgetMovable
285
                    | QDockWidget::DockWidgetFloatable);
286

287
    d->_dockedWindows.push_back(dw);
288

289
    if (d->overlayManager) {
290
        d->overlayManager->initDockWidget(dw);
291
    }
292

293
    connect(dw->toggleViewAction(), &QAction::triggered, [this, dw](){
294
        Base::ConnectionBlocker block(d->_connParam);
295
        QByteArray dockName = dw->toggleViewAction()->data().toByteArray();
296
        d->_hPref->SetBool(dockName.constData(), dw->isVisible());
297
    });
298

299
    auto cb = []() {getMainWindow()->saveWindowSettings(true);};
300
    connect(dw, &QDockWidget::topLevelChanged, cb);
301
    connect(dw, &QDockWidget::dockLocationChanged, cb);
302
    return dw;
303
}
304

305
/**
306
 * Returns the widget inside the dock window by name.
307
 * If it does not exist 0 is returned.
308
 */
309
QWidget* DockWindowManager::getDockWindow(const char* name) const
310
{
311
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
312
        if ((*it)->objectName() == QString::fromUtf8(name))
313
            return (*it)->widget();
314
    }
315

316
    return nullptr;
317
}
318

319
/**
320
 * Returns the dock widget by name.
321
 * If it does not exist 0 is returned.
322
 */
323
QDockWidget* DockWindowManager::getDockContainer(const char* name) const
324
{
325
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
326
        if ((*it)->objectName() == QLatin1String(name))
327
            return (*it);
328
    }
329

330
    return nullptr;
331
}
332

333
/**
334
 * Returns a list of all widgets inside the dock windows.
335
 */
336
QList<QWidget*> DockWindowManager::getDockWindows() const
337
{
338
    QList<QWidget*> docked;
339
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it)
340
        docked.push_back((*it)->widget());
341
    return docked;
342
}
343

344
/**
345
 * Removes the specified dock window with name \name without deleting it.
346
 */
347
QWidget* DockWindowManager::removeDockWindow(const char* name)
348
{
349
    QWidget* widget=nullptr;
350
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
351
        if ((*it)->objectName() == QString::fromUtf8(name)) {
352
            QDockWidget* dw = *it;
353
            d->_dockedWindows.erase(it);
354

355
            if (d->overlayManager) {
356
                d->overlayManager->unsetupDockWidget(dw);
357
            }
358

359
            getMainWindow()->removeDockWidget(dw);
360
            // avoid to destruct the embedded widget
361
            widget = dw->widget();
362
            widget->setParent(nullptr);
363
            dw->setWidget(nullptr);
364
            disconnect(dw, &QObject::destroyed,
365
                       this, &DockWindowManager::onDockWidgetDestroyed);
366
            disconnect(widget, &QObject::destroyed,
367
                       this, &DockWindowManager::onWidgetDestroyed);
368
            delete dw; // destruct the QDockWidget, i.e. the parent of the widget
369
            break;
370
        }
371
    }
372

373
    return widget;
374
}
375

376
/**
377
 * Method provided for convenience. Does basically the same as the method above unless that
378
 * it accepts a pointer.
379
 */
380
void DockWindowManager::removeDockWindow(QWidget* widget)
381
{
382
    if (!widget)
383
        return;
384
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
385
        if ((*it)->widget() == widget) {
386
            QDockWidget* dw = *it;
387
            d->_dockedWindows.erase(it);
388
            if (d->overlayManager) {
389
                d->overlayManager->unsetupDockWidget(dw);
390
            }
391
            getMainWindow()->removeDockWidget(dw);
392
            // avoid to destruct the embedded widget
393
            widget->setParent(nullptr);
394
            dw->setWidget(nullptr);
395
            disconnect(dw, &QObject::destroyed,
396
                       this, &DockWindowManager::onDockWidgetDestroyed);
397
            disconnect(widget, &QObject::destroyed,
398
                       this, &DockWindowManager::onWidgetDestroyed);
399
            delete dw; // destruct the QDockWidget, i.e. the parent of the widget
400
            break;
401
        }
402
    }
403
}
404

405
/**
406
 * If the corresponding dock widget isn't visible then activate it.
407
 */
408
void DockWindowManager::activate(QWidget* widget)
409
{
410
    QDockWidget* dw = nullptr;
411
    QWidget* par = widget->parentWidget();
412
    while (par) {
413
        dw = qobject_cast<QDockWidget*>(par);
414
        if (dw) {
415
            break;
416
        }
417
        par = par->parentWidget();
418
    }
419

420
    if (!dw)
421
        return;
422

423
    if (!dw->toggleViewAction()->isChecked()) {
424
        dw->toggleViewAction()->activate(QAction::Trigger);
425
    }
426

427
    dw->raise();
428
}
429

430
/**
431
 * Sets the window title for the dockable windows.
432
 */
433
void DockWindowManager::retranslate()
434
{
435
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
436
        QString title = (*it)->windowTitle();
437
        if (title.isEmpty())
438
            (*it)->setWindowTitle(QDockWidget::tr((*it)->objectName().toUtf8()));
439
        else
440
            (*it)->setWindowTitle(title);
441
    }
442
}
443

444
/**
445
 * Appends a new \a widget with \a name to the list of available dock widgets. The caller must make sure that
446
 * the name is unique. If a widget with this name is already registered nothing is done but false is returned,
447
 * otherwise it is appended and true is returned.
448
 *
449
 * As default the following widgets are already registered:
450
 * \li Std_TreeView
451
 * \li Std_PropertyView
452
 * \li Std_ReportView
453
 * \li Std_ToolBox
454
 * \li Std_ComboView
455
 * \li Std_SelectionView
456
 *
457
 * To avoid name clashes the caller should use names of the form \a module_widgettype, i. e. if a analyse dialog for
458
 * the mesh module is added the name must then be Mesh_AnalyzeDialog.
459
 *
460
 * To make use of dock windows when a workbench gets loaded the method setupDockWindows() must reimplemented in a
461
 * subclass of Gui::Workbench.
462
 */
463
bool DockWindowManager::registerDockWindow(const char* name, QWidget* widget)
464
{
465
    QMap<QString, QPointer<QWidget> >::Iterator it = d->_dockWindows.find(QString::fromUtf8(name));
466
    if (it != d->_dockWindows.end() || !widget)
467
        return false;
468
    d->_dockWindows[QString::fromUtf8(name)] = widget;
469
    widget->hide(); // hide the widget if not used
470
    return true;
471
}
472

473
QWidget* DockWindowManager::unregisterDockWindow(const char* name)
474
{
475
    QWidget* widget = nullptr;
476
    QMap<QString, QPointer<QWidget> >::Iterator it = d->_dockWindows.find(QString::fromUtf8(name));
477
    if (it != d->_dockWindows.end()) {
478
        widget = d->_dockWindows.take(QString::fromUtf8(name));
479
    }
480

481
    return widget;
482
}
483

484
QWidget* DockWindowManager::findRegisteredDockWindow(const char* name)
485
{
486
    QMap<QString, QPointer<QWidget> >::Iterator it = d->_dockWindows.find(QString::fromUtf8(name));
487
    if (it != d->_dockWindows.end())
488
        return it.value();
489
    return nullptr;
490
}
491

492
/** Sets up the dock windows of the activated workbench. */
493
void DockWindowManager::setup(DockWindowItems* items)
494
{
495
    // save state of current dock windows
496
    saveState();
497
    d->_dockWindowItems = *items;
498

499
    QList<QDockWidget*> docked = d->_dockedWindows;
500
    const QList<DockWindowItem>& dws = items->dockWidgets();
501
    for (const auto& it : dws) {
502
        QDockWidget* dw = findDockWidget(docked, it.name);
503
        QByteArray dockName = it.name.toLatin1();
504
        bool visible = d->_hPref->GetBool(dockName.constData(), it.visibility);
505

506
        if (!dw) {
507
            QMap<QString, QPointer<QWidget> >::Iterator jt = d->_dockWindows.find(it.name);
508
            if (jt != d->_dockWindows.end()) {
509
                dw = addDockWindow(jt.value()->objectName().toUtf8(), jt.value(), it.pos);
510
                jt.value()->show();
511
                dw->toggleViewAction()->setData(it.name);
512
                dw->setVisible(visible);
513
            }
514
        }
515
        else {
516
            dw->setVisible(visible);
517
            dw->toggleViewAction()->setVisible(true);
518
            int index = docked.indexOf(dw);
519
            docked.removeAt(index);
520
        }
521

522
        if (d->overlayManager && dw && visible) {
523
            d->overlayManager->setupDockWidget(dw);
524
        }
525
    }
526

527
    tabifyDockWidgets(items);
528
}
529

530
void DockWindowManager::tabifyDockWidgets(DockWindowItems* items)
531
{
532
    // Tabify dock widgets only once to avoid to override the current layout
533
    // in case it was modified by the user. The user shouldn't be forced to
534
    // restore a possibly changed layout after switching to another workbench.
535
    static bool tabify = false;
536
    if (tabify) {
537
        return;
538
    }
539

540
    std::array<QList<QDockWidget*>, 4> areas;
541
    const QList<DockWindowItem>& dws = items->dockWidgets();
542
    QList<QDockWidget*> docked = d->_dockedWindows;
543
    for (const auto& it : dws) {
544
        QDockWidget* dw = findDockWidget(docked, it.name);
545
        if (it.tabbed && dw) {
546
            Qt::DockWidgetArea pos = getMainWindow()->dockWidgetArea(dw);
547
            switch (pos) {
548
                case Qt::LeftDockWidgetArea:
549
                    areas[0] << dw;
550
                    break;
551
                case Qt::RightDockWidgetArea:
552
                    areas[1] << dw;
553
                    break;
554
                case Qt::TopDockWidgetArea:
555
                    areas[2] << dw;
556
                    break;
557
                case Qt::BottomDockWidgetArea:
558
                    areas[3] << dw;
559
                    break;
560
                default:
561
                    break;
562
            }
563
        }
564
    }
565

566
    // tabify dock widgets for which "tabbed" is true and which have the same position
567
    for (auto& area : areas) {
568
        for (auto it : area) {
569
            if (it != area.front()) {
570
                getMainWindow()->tabifyDockWidget(area.front(), it);
571
                tabify = true;
572
            }
573
        }
574

575
        // activate the first of the tabbed dock widgets
576
        if (area.size() > 1) {
577
            area.front()->raise();
578
        }
579
    }
580
}
581

582
void DockWindowManager::saveState()
583
{
584
    const QList<DockWindowItem>& dockItems = d->_dockWindowItems.dockWidgets();
585
    for (QList<DockWindowItem>::ConstIterator it = dockItems.begin(); it != dockItems.end(); ++it) {
586
        QDockWidget* dw = findDockWidget(d->_dockedWindows, it->name);
587
        if (dw) {
588
            QByteArray dockName = dw->toggleViewAction()->data().toByteArray();
589
            d->_hPref->SetBool(dockName.constData(), dw->isVisible());
590
        }
591
    }
592
}
593

594
void DockWindowManager::loadState()
595
{
596
    ParameterGrp::handle hPref = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
597
        ->GetGroup("MainWindow")->GetGroup("DockWindows");
598
    const QList<DockWindowItem>& dockItems = d->_dockWindowItems.dockWidgets();
599
    for (QList<DockWindowItem>::ConstIterator it = dockItems.begin(); it != dockItems.end(); ++it) {
600
        QDockWidget* dw = findDockWidget(d->_dockedWindows, it->name);
601
        if (dw) {
602
            QByteArray dockName = it->name.toUtf8();
603
            bool visible = hPref->GetBool(dockName.constData(), it->visibility);
604
            dw->setVisible(visible);
605
        }
606
    }
607
}
608

609
QDockWidget* DockWindowManager::findDockWidget(const QList<QDockWidget*>& dw, const QString& name) const
610
{
611
    for (QList<QDockWidget*>::ConstIterator it = dw.begin(); it != dw.end(); ++it) {
612
        if ((*it)->toggleViewAction()->data().toString() == name)
613
            return *it;
614
    }
615

616
    return nullptr;
617
}
618

619
void DockWindowManager::onDockWidgetDestroyed(QObject* dw)
620
{
621
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
622
        if (*it == dw) {
623
            d->_dockedWindows.erase(it);
624
            break;
625
        }
626
    }
627
}
628

629
void DockWindowManager::onWidgetDestroyed(QObject* widget)
630
{
631
    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end(); ++it) {
632
        // make sure that the dock widget is not about to being deleted
633
        if ((*it)->metaObject() != &QDockWidget::staticMetaObject) {
634
            disconnect(*it, &QObject::destroyed,
635
                       this, &DockWindowManager::onDockWidgetDestroyed);
636
            d->_dockedWindows.erase(it);
637
            break;
638
        }
639

640
        if ((*it)->widget() == widget) {
641
            // Delete the widget if not used anymore
642
            QDockWidget* dw = *it;
643
            dw->deleteLater();
644
            break;
645
        }
646
    }
647
}
648

649
#include "moc_DockWindowManager.cpp"
650

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.