diff --git a/sublime/idealbuttonbarwidget.h b/sublime/idealbuttonbarwidget.h --- a/sublime/idealbuttonbarwidget.h +++ b/sublime/idealbuttonbarwidget.h @@ -47,6 +47,8 @@ QAction* addWidget(IdealDockWidget *widget, Area* area, View *view); + + void addAction(QAction *action); void removeAction(QAction* action); Qt::Orientation orientation() const; @@ -64,21 +66,30 @@ void saveShowState(); bool lastShowState(); + void loadOrderSettings(); + void saveOrderSettings(); + private Q_SLOTS: void showWidget(bool checked); void buttonPressed(bool state); signals: void emptyChanged(); -protected: - void actionEvent(QActionEvent *event) override; - private: + void applyOrderToLayout(); + void takeOrderFromLayout(); + + void shiftButton(const QString& id, int direction); + + IdealToolButton* button(const QString& id); + QString id(const IdealToolButton* button); + Qt::DockWidgetArea _area; IdealController *_controller; QWidget *_corner; bool _showState; + QStringList _buttonsOrder; }; } diff --git a/sublime/idealbuttonbarwidget.cpp b/sublime/idealbuttonbarwidget.cpp --- a/sublime/idealbuttonbarwidget.cpp +++ b/sublime/idealbuttonbarwidget.cpp @@ -71,6 +71,10 @@ refreshText(); } + QString id() { + return m_dock->view()->document()->documentSpecifier(); + } + private: bool eventFilter(QObject * watched, QEvent * event) override { @@ -124,6 +128,11 @@ (void) new IdealButtonBarLayout(orientation(), this); } +QWidget* IdealButtonBarWidget::corner() +{ + return _corner; +} + QAction* IdealButtonBarWidget::addWidget(IdealDockWidget *dock, Area *area, View *view) { @@ -134,26 +143,73 @@ dock->setView(view); dock->setDockWidgetArea(_area); - bool wasEmpty = actions().isEmpty(); - auto action = new ToolViewAction(dock, this); addAction(action); - if(wasEmpty) - emit emptyChanged(); return action; } -QWidget* IdealButtonBarWidget::corner() +void IdealButtonBarWidget::addAction(QAction* qaction) { - return _corner; + QWidget::addAction(qaction); + + auto action = dynamic_cast(qaction); + if (!action || action->button()) + return; + + bool wasEmpty = isEmpty(); + + IdealToolButton *button = new IdealToolButton(_area); + //apol: here we set the usual width of a button for the vertical toolbars as the minimumWidth + //this is done because otherwise when we remove all the buttons and re-add new ones we get all + //the screen flickering. This is solved by not defaulting to a smaller width when it's empty + int w = button->sizeHint().width(); + if (orientation() == Qt::Vertical && w > minimumWidth()) + setMinimumWidth(w); + + action->setButton(button); + button->setDefaultAction(action); + + Q_ASSERT(action->dockWidget()); + + connect(action, &QAction::toggled, this, static_cast(&IdealButtonBarWidget::showWidget)); + connect(button, &IdealToolButton::clicked, this, &IdealButtonBarWidget::buttonPressed); + connect(button, &IdealToolButton::customContextMenuRequested, + action->dockWidget(), &IdealDockWidget::contextMenuRequested); + + connect(action->dockWidget(), &IdealDockWidget::shiftLeftRequested, this, [this](const QString& id) { + const int direction = (_area == Qt::BottomDockWidgetArea) ? 1 : -1; + shiftButton(id, direction); + }); + connect(action->dockWidget(), &IdealDockWidget::shiftRightRequested, this, [this](const QString& id) { + const int direction = (_area == Qt::BottomDockWidgetArea) ? -1 : 1; + shiftButton(id, direction); + }); + + QString buttonId = id(button); + if (!_buttonsOrder.contains(buttonId)) { + if (_area == Qt::BottomDockWidgetArea) + _buttonsOrder.push_front(buttonId); + else + _buttonsOrder.push_back(buttonId); + } + applyOrderToLayout(); + + if (wasEmpty) { + emit emptyChanged(); + } } -void IdealButtonBarWidget::removeAction(QAction * widgetAction) +void IdealButtonBarWidget::removeAction(QAction* widgetAction) { + QWidget::removeAction(widgetAction); + auto action = dynamic_cast(widgetAction); delete action->button(); delete action; + + if(layout()->isEmpty()) + emit emptyChanged(); } bool IdealButtonBarWidget::isEmpty() @@ -177,6 +233,85 @@ return _showState; } +QString IdealButtonBarWidget::id(const IdealToolButton* button) +{ + foreach (QAction* a, actions()) { + auto tva = dynamic_cast(a); + if (tva && tva->button() == button) + return tva->id(); + } + + return QString(); +} + +IdealToolButton* Sublime::IdealButtonBarWidget::button(const QString& id) +{ + foreach (QAction* a, actions()) { + auto tva = dynamic_cast(a); + if (tva && tva->id() == id) + return tva->button(); + } + + return nullptr; +} + +void IdealButtonBarWidget::loadOrderSettings() +{ + KConfigGroup config = KSharedConfig::openConfig()->group("UI"); + _buttonsOrder = config.readEntry(QStringLiteral("(%1) Tool Views Order").arg(_area), QStringList()); + + applyOrderToLayout(); +} + +void IdealButtonBarWidget::saveOrderSettings() +{ + takeOrderFromLayout(); + + KConfigGroup config = KSharedConfig::openConfig()->group("UI"); + config.writeEntry(QStringLiteral("(%1) Tool Views Order").arg(_area), _buttonsOrder); +} + +void IdealButtonBarWidget::applyOrderToLayout() +{ + foreach(auto a, actions()) { + if (auto tva = dynamic_cast(a)) + layout()->removeWidget(tva->button()); + } + + foreach(auto id, _buttonsOrder) { + if (auto b = button(id)) + layout()->addWidget(b); + } +} + +void IdealButtonBarWidget::takeOrderFromLayout() +{ + _buttonsOrder.clear(); + for (int i = 0; i < layout()->count(); ++i) { + if (auto button = dynamic_cast(layout()->itemAt(i)->widget())) + _buttonsOrder += id(button); + } +} + +void IdealButtonBarWidget::shiftButton(const QString& id, int direction) +{ + int index = _buttonsOrder.indexOf(id); + if (index < 0) + return; + + while ((index + direction) >= 0 && (index + direction) < _buttonsOrder.size()) { + _buttonsOrder.swap(index, index + direction); + + // Skip missing and not-displayed buttons stored in the current order. + // Example for not-displayed button - "Build" tool view, which is hidden at KDevelop start. + if (button(_buttonsOrder.at(index))) + break; + index += direction; + } + + applyOrderToLayout(); +} + Qt::Orientation IdealButtonBarWidget::orientation() const { if (_area == Qt::LeftDockWidgetArea || _area == Qt::RightDockWidgetArea) @@ -228,63 +363,6 @@ button->setChecked(checked); } - -void IdealButtonBarWidget::actionEvent(QActionEvent *event) -{ - auto action = dynamic_cast(event->action()); - if (!action) - return; - - switch (event->type()) { - case QEvent::ActionAdded: { - bool wasEmpty = isEmpty(); - if (!action->button()) { - IdealToolButton *button = new IdealToolButton(_area); - //apol: here we set the usual width of a button for the vertical toolbars as the minimumWidth - //this is done because otherwise when we remove all the buttons and re-add new ones we get all - //the screen flickering. This is solved by not defaulting to a smaller width when it's empty - int w = button->sizeHint().width(); - if(orientation()==Qt::Vertical && w>minimumWidth()) - setMinimumWidth(w); - action->setButton(button); - button->setDefaultAction(action); - - Q_ASSERT(action->dockWidget()); - - layout()->addWidget(button); - connect(action, &QAction::toggled, this, static_cast(&IdealButtonBarWidget::showWidget)); - connect(button, &IdealToolButton::clicked, this, &IdealButtonBarWidget::buttonPressed); - connect(button, &IdealToolButton::customContextMenuRequested, - action->dockWidget(), &IdealDockWidget::contextMenuRequested); - if ( wasEmpty ) { - emit emptyChanged(); - } - } - } break; - - case QEvent::ActionRemoved: { - IdealToolButton *button = action->button(); - if (button) { - for (int index = 0; index < layout()->count(); ++index) { - if (QLayoutItem *item = layout()->itemAt(index)) { - if (item->widget() == button) { - action->disconnect(this); - delete layout()->takeAt(index); - break; - } - } - } - } - if(layout()->isEmpty()) { - emit emptyChanged(); - } - } break; - - default: - break; - } -} - IdealDockWidget * IdealButtonBarWidget::widgetForAction(QAction *_action) const { return dynamic_cast(_action)->dockWidget(); diff --git a/sublime/idealdockwidget.h b/sublime/idealdockwidget.h --- a/sublime/idealdockwidget.h +++ b/sublime/idealdockwidget.h @@ -51,6 +51,9 @@ Q_SIGNALS: void closeRequested(); + void shiftLeftRequested(const QString& id); + void shiftRightRequested(const QString& id); + private Q_SLOTS: void slotRemove(); diff --git a/sublime/idealdockwidget.cpp b/sublime/idealdockwidget.cpp --- a/sublime/idealdockwidget.cpp +++ b/sublime/idealdockwidget.cpp @@ -136,6 +136,19 @@ menu.addSeparator(); + QAction *shiftLeft = nullptr; + QAction *shiftRight = nullptr; + if (!isFloating()) { + if (m_docking_area == Qt::BottomDockWidgetArea) { + shiftLeft = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Shift Left")); + shiftRight = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("Shift Right")); + } else { + shiftLeft = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Shift Up")); + shiftRight = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Shift Down")); + } + menu.addSeparator(); + } + QAction *setShortcut = menu.addAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("Assign Shortcut...")); setShortcut->setToolTip(i18n("Use this shortcut to trigger visibility of the toolview.")); @@ -179,6 +192,12 @@ setFloating(true); m_area->raiseToolView(m_view); return; + } else if ( triggered == shiftLeft ) { + emit shiftLeftRequested(m_view->document()->documentSpecifier()); + return; + } else if ( triggered == shiftRight ) { + emit shiftRightRequested(m_view->document()->documentSpecifier()); + return; } if (isFloating()) { diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -522,6 +522,15 @@ void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { + static bool orderIsLoaded = false; + + if (!orderIsLoaded) { + orderIsLoaded = true; + idealController->leftBarWidget->loadOrderSettings(); + idealController->bottomBarWidget->loadOrderSettings(); + idealController->rightBarWidget->loadOrderSettings(); + } + if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); @@ -571,6 +580,15 @@ void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { + static bool orderIsSaved = false; + + if (!orderIsSaved) { + orderIsSaved = true; + idealController->leftBarWidget->saveOrderSettings(); + idealController->bottomBarWidget->saveOrderSettings(); + idealController->rightBarWidget->saveOrderSettings(); + } + QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return;