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"