File indexing completed on 2024-04-28 04:37:31

0001 /*
0002     SPDX-FileCopyrightText: 2007 Roberto Raggi <roberto@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0004     SPDX-FileCopyrightText: 2011 Alexander Dymo <adymo@kdevelop.org>
0005 
0006     SPDX-License-Identifier: LicenseRef-MIT-KDevelop-Ideal
0007 */
0008 
0009 #include "idealbuttonbarwidget.h"
0010 #include "mainwindow.h"
0011 #include "idealdockwidget.h"
0012 #include "ideallayout.h"
0013 #include "idealtoolbutton.h"
0014 #include "document.h"
0015 #include "view.h"
0016 
0017 #include <KLocalizedString>
0018 #include <KSharedConfig>
0019 #include <KConfigGroup>
0020 
0021 #include <QBoxLayout>
0022 #include <QApplication>
0023 
0024 using namespace Sublime;
0025 
0026 class ToolViewAction : public QAction
0027 {
0028     Q_OBJECT
0029 
0030 public:
0031     ToolViewAction(IdealDockWidget *dock, QObject* parent) : QAction(parent), m_dock(dock)
0032     {
0033         setCheckable(true);
0034 
0035         const QString title = dock->view()->document()->title();
0036         setIcon(dock->windowIcon());
0037         setToolTip(i18nc("@info:tooltip", "Toggle '%1' tool view", title));
0038         setText(title);
0039 
0040         //restore tool view shortcut config
0041         KConfigGroup config = KSharedConfig::openConfig()->group("UI");
0042         QStringList shortcutStrings = config.readEntry(QStringLiteral("Shortcut for %1").arg(title), QStringList());
0043         setShortcuts({ QKeySequence::fromString(shortcutStrings.value(0)), QKeySequence::fromString(shortcutStrings.value(1)) });
0044 
0045         dock->setWindowTitle(title);
0046         dock->view()->widget()->installEventFilter(this);
0047         refreshText();
0048     }
0049 
0050     IdealDockWidget *dockWidget() const
0051     {
0052         Q_ASSERT(m_dock);
0053         return m_dock;
0054     }
0055 
0056     IdealToolButton* button() { return m_button; }
0057     void setButton(IdealToolButton* button) {
0058         m_button = button;
0059         refreshText();
0060     }
0061 
0062     QString id() {
0063         return m_dock->view()->document()->documentSpecifier();
0064     }
0065 
0066 private:
0067     bool eventFilter(QObject * watched, QEvent * event) override
0068     {
0069         // an event may arrive when m_dock->view()->widget() is already destroyed
0070         // so check for event type first.
0071         if (event->type() == QEvent::EnabledChange && watched == m_dock->view()->widget()) {
0072             refreshText();
0073         }
0074 
0075         return QAction::eventFilter(watched, event);
0076     }
0077 
0078     void refreshText()
0079     {
0080         const auto widget = m_dock->view()->widget();
0081         const QString title = m_dock->view()->document()->title();
0082         setText(widget->isEnabled() ? title : QStringLiteral("(%1)").arg(title));
0083     }
0084 
0085     QPointer<IdealDockWidget> m_dock;
0086     QPointer<IdealToolButton> m_button;
0087 };
0088 
0089 IdealButtonBarWidget::IdealButtonBarWidget(Qt::DockWidgetArea area,
0090         IdealController *controller, Sublime::MainWindow *parent)
0091     : QWidget(parent)
0092     , m_area(area)
0093     , m_controller(controller)
0094     , m_corner(nullptr)
0095     , m_showState(false)
0096     , m_buttonsLayout(nullptr)
0097 {
0098     setContextMenuPolicy(Qt::CustomContextMenu);
0099     setToolTip(i18nc("@info:tooltip", "Right click to add new tool views."));
0100 
0101     m_buttonsLayout = new IdealButtonBarLayout(orientation(), this);
0102     if (area == Qt::BottomDockWidgetArea)
0103     {
0104         auto *statusLayout = new QBoxLayout(QBoxLayout::LeftToRight, this);
0105         statusLayout->setContentsMargins(0, 0, 0, 0);
0106 
0107         statusLayout->addLayout(m_buttonsLayout);
0108 
0109         statusLayout->addStretch(1);
0110 
0111         m_corner = new QWidget(this);
0112         auto *cornerLayout = new QBoxLayout(QBoxLayout::LeftToRight, m_corner);
0113         cornerLayout->setContentsMargins(0, 0, 0, 0);
0114         cornerLayout->setSpacing(0);
0115         statusLayout->addWidget(m_corner);
0116     } else {
0117         auto *superLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
0118         superLayout->setContentsMargins(0, 0, 0, 0);
0119 
0120         superLayout->addLayout(m_buttonsLayout);
0121         superLayout->addStretch(1);
0122     }
0123 }
0124 
0125 QAction* IdealButtonBarWidget::addWidget(IdealDockWidget *dock,
0126                                          Area *area, View *view)
0127 {
0128     if (m_area == Qt::BottomDockWidgetArea || m_area == Qt::TopDockWidgetArea)
0129         dock->setFeatures( dock->features() | QDockWidget::DockWidgetVerticalTitleBar );
0130 
0131     dock->setArea(area);
0132     dock->setView(view);
0133     dock->setDockWidgetArea(m_area);
0134 
0135     auto action = new ToolViewAction(dock, this);
0136     addAction(action);
0137 
0138     return action;
0139 }
0140 
0141 QWidget* IdealButtonBarWidget::corner() const
0142 {
0143     return m_corner;
0144 }
0145 
0146 void IdealButtonBarWidget::addAction(QAction* qaction)
0147 {
0148     QWidget::addAction(qaction);
0149 
0150     auto action = qobject_cast<ToolViewAction*>(qaction);
0151     if (!action || action->button()) {
0152       return;
0153     }
0154 
0155     bool wasEmpty = isEmpty();
0156 
0157     auto *button = new IdealToolButton(m_area);
0158     //apol: here we set the usual width of a button for the vertical toolbars as the minimumWidth
0159     //this is done because otherwise when we remove all the buttons and re-add new ones we get all
0160     //the screen flickering. This is solved by not defaulting to a smaller width when it's empty
0161     int w = button->sizeHint().width();
0162     if (orientation() == Qt::Vertical && w > minimumWidth()) {
0163         setMinimumWidth(w);
0164     }
0165 
0166     action->setButton(button);
0167     button->setDefaultAction(action);
0168 
0169     Q_ASSERT(action->dockWidget());
0170 
0171     connect(action, &QAction::toggled, this, QOverload<bool>::of(&IdealButtonBarWidget::showWidget));
0172     connect(button, &IdealToolButton::customContextMenuRequested,
0173             action->dockWidget(), &IdealDockWidget::contextMenuRequested);
0174 
0175     addButtonToOrder(button);
0176     applyOrderToLayout();
0177 
0178     if (wasEmpty) {
0179         emit emptyChanged();
0180     }
0181 }
0182 
0183 void IdealButtonBarWidget::removeAction(QAction* widgetAction)
0184 {
0185     QWidget::removeAction(widgetAction);
0186 
0187     auto action = static_cast<ToolViewAction*>(widgetAction);
0188     action->button()->deleteLater();
0189     delete action;
0190 
0191     if (m_buttonsLayout->isEmpty()) {
0192         emit emptyChanged();
0193     }
0194 }
0195 
0196 bool IdealButtonBarWidget::isEmpty() const
0197 {
0198     return actions().isEmpty();
0199 }
0200 
0201 bool IdealButtonBarWidget::isShown() const
0202 {
0203     const auto actions = this->actions();
0204     return std::any_of(actions.cbegin(), actions.cend(),
0205                        [](const QAction* action){ return action->isChecked(); });
0206 }
0207 
0208 void IdealButtonBarWidget::saveShowState()
0209 {
0210     m_showState = isShown();
0211 }
0212 
0213 bool IdealButtonBarWidget::lastShowState()
0214 {
0215     return m_showState;
0216 }
0217 
0218 QString IdealButtonBarWidget::id(const IdealToolButton* button) const
0219 {
0220     const auto actions = this->actions();
0221     for (QAction* a : actions) {
0222         auto tva = qobject_cast<ToolViewAction*>(a);
0223         if (tva && tva->button() == button) {
0224             return tva->id();
0225         }
0226     }
0227 
0228     return QString();
0229 }
0230 
0231 IdealToolButton* IdealButtonBarWidget::button(const QString& id) const
0232 {
0233     const auto actions = this->actions();
0234     for (QAction* a : actions) {
0235         auto tva = qobject_cast<ToolViewAction*>(a);
0236         if (tva && tva->id() == id) {
0237             return tva->button();
0238         }
0239     }
0240 
0241     return nullptr;
0242 }
0243 
0244 void IdealButtonBarWidget::addButtonToOrder(const IdealToolButton* button)
0245 {
0246     QString buttonId = id(button);
0247     if (!m_buttonsOrder.contains(buttonId)) {
0248         m_buttonsOrder.push_back(buttonId);
0249     }
0250 }
0251 
0252 void IdealButtonBarWidget::loadOrderSettings(const KConfigGroup& configGroup)
0253 {
0254     m_buttonsOrder = configGroup.readEntry(QStringLiteral("(%1) Tool Views Order").arg(m_area), QStringList());
0255     applyOrderToLayout();
0256 }
0257 
0258 void IdealButtonBarWidget::saveOrderSettings(KConfigGroup& configGroup)
0259 {
0260     takeOrderFromLayout();
0261     configGroup.writeEntry(QStringLiteral("(%1) Tool Views Order").arg(m_area), m_buttonsOrder);
0262 }
0263 
0264 bool IdealButtonBarWidget::isLocked() const
0265 {
0266     KConfigGroup config = KSharedConfig::openConfig()->group("UI");
0267     return config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(m_area), false);
0268 }
0269 
0270 void IdealButtonBarWidget::applyOrderToLayout()
0271 {
0272     // If widget already have some buttons in the layout then calling loadOrderSettings() may leads
0273     // to situations when loaded order does not contains all existing buttons. Therefore we should
0274     // fix this with using addToOrder() method.
0275     for (int i = 0; i < m_buttonsLayout->count(); ++i) {
0276         if (auto button = qobject_cast<IdealToolButton*>(m_buttonsLayout->itemAt(i)->widget())) {
0277             addButtonToOrder(button);
0278             m_buttonsLayout->removeWidget(button);
0279             --i;
0280         }
0281     }
0282 
0283     for (const QString& id : qAsConst(m_buttonsOrder)) {
0284         if (auto b = button(id)) {
0285             m_buttonsLayout->addWidget(b);
0286         }
0287     }
0288 }
0289 
0290 void IdealButtonBarWidget::takeOrderFromLayout()
0291 {
0292     m_buttonsOrder.clear();
0293     for (int i = 0; i < m_buttonsLayout->count(); ++i) {
0294         if (auto button = qobject_cast<IdealToolButton*>(m_buttonsLayout->itemAt(i)->widget())) {
0295             m_buttonsOrder += id(button);
0296         }
0297     }
0298 }
0299 
0300 Qt::Orientation IdealButtonBarWidget::orientation() const
0301 {
0302     if (m_area == Qt::LeftDockWidgetArea || m_area == Qt::RightDockWidgetArea)
0303         return Qt::Vertical;
0304 
0305     return Qt::Horizontal;
0306 }
0307 
0308 Qt::DockWidgetArea IdealButtonBarWidget::area() const
0309 {
0310     return m_area;
0311 }
0312 
0313 void IdealButtonBarWidget::showWidget(bool checked)
0314 {
0315     Q_ASSERT(parentWidget() != nullptr);
0316 
0317     auto *action = qobject_cast<QAction *>(sender());
0318     Q_ASSERT(action);
0319 
0320     showWidget(action, checked);
0321 }
0322 
0323 void IdealButtonBarWidget::showWidget(QAction *action, bool checked)
0324 {
0325     auto widgetAction = static_cast<ToolViewAction*>(action);
0326 
0327     IdealToolButton* button = widgetAction->button();
0328     Q_ASSERT(button);
0329 
0330     if (checked) {
0331         if ( !QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ) {
0332             // Make sure only one widget is visible at any time.
0333             // The alternative to use a QActionCollection and setting that to "exclusive"
0334             // has a big drawback: QActions in a collection that is exclusive cannot
0335             // be un-checked by the user, e.g. in the View -> Tool Views menu.
0336             const auto actions = this->actions();
0337             for (QAction* otherAction : actions) {
0338                 if ( otherAction != widgetAction && otherAction->isChecked() )
0339                     otherAction->setChecked(false);
0340             }
0341         }
0342 
0343         m_controller->lastDockWidget[m_area] = widgetAction->dockWidget();
0344     }
0345 
0346     m_controller->showDockWidget(widgetAction->dockWidget(), checked);
0347     widgetAction->setChecked(checked);
0348     button->setChecked(checked);
0349 }
0350 
0351 IdealDockWidget * IdealButtonBarWidget::widgetForAction(QAction *_action) const
0352 {
0353     return static_cast<ToolViewAction*>(_action)->dockWidget();
0354 }
0355 
0356 #include "idealbuttonbarwidget.moc"
0357 #include "moc_idealbuttonbarwidget.cpp"