diff options
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/common')
37 files changed, 2816 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp new file mode 100644 index 0000000..005de4c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp @@ -0,0 +1,39 @@ +#include "colorbutton.h" +#include <QResizeEvent> +#include <QColorDialog> + +ColorButton::ColorButton(QWidget *parent) : + QPushButton(parent) +{ + setFixedWidth(height()*2); + setColor(Qt::black); + connect(this, SIGNAL(clicked()), this, SLOT(pickColor())); +} + +QColor ColorButton::getColor() const +{ + return color; +} + +void ColorButton::setColor(const QColor& value) +{ + color = value; + QPixmap pix(iconSize()); + pix.fill(color); + setIcon(pix); + emit colorChanged(color); +} + +void ColorButton::pickColor() +{ + QColor newColor = QColorDialog::getColor(color, parentWidget(), tr("Pick a color")); + if (!newColor.isValid()) + return; + + setColor(newColor); +} + +void ColorButton::resizeEvent(QResizeEvent* e) +{ + setFixedWidth(e->size().height()*2); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h new file mode 100644 index 0000000..237a4ee --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h @@ -0,0 +1,30 @@ +#ifndef COLORBUTTON_H +#define COLORBUTTON_H + +#include "guiSQLiteStudio_global.h" +#include <QPushButton> +#include <QColor> + +class GUI_API_EXPORT ColorButton : public QPushButton +{ + Q_OBJECT + public: + explicit ColorButton(QWidget *parent = 0); + + QColor getColor() const; + void setColor(const QColor& value); + + protected: + void resizeEvent(QResizeEvent* e); + + private: + QColor color; + + private slots: + void pickColor(); + + signals: + void colorChanged(const QColor& color); +}; + +#endif // COLORBUTTON_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp new file mode 100644 index 0000000..aa2f115 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp @@ -0,0 +1,16 @@ +#include "configcombobox.h" + +ConfigComboBox::ConfigComboBox(QWidget *parent) : + QComboBox(parent) +{ +} + +QVariant ConfigComboBox::getModelName() const +{ + return modelName; +} + +void ConfigComboBox::setModelName(QVariant arg) +{ + modelName = arg; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h new file mode 100644 index 0000000..dd4c71c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h @@ -0,0 +1,34 @@ +#ifndef CONFIGCOMBOBOX_H +#define CONFIGCOMBOBOX_H + +#include "guiSQLiteStudio_global.h" +#include <QComboBox> + +/** + * @brief Config-oriented combo box. + * + * It's just like a regular QComboBox, except it honors additional Qt dynamic property + * called "modelName". The "modelName" property should name a CfgEntry key (together with its category, + * just like "cfg" properties for CfgEntry linked widgets), that is of QStringList type. + * The QStringList is used as a data model for QComboBox. Every time that the CfgEntry + * with QStringList changes, the combo box data entries are updated. + */ +class GUI_API_EXPORT ConfigComboBox : public QComboBox +{ + Q_OBJECT + + Q_PROPERTY(QVariant modelName READ getModelName WRITE setModelName) + + public: + explicit ConfigComboBox(QWidget* parent = 0); + + QVariant getModelName() const; + + public slots: + void setModelName(QVariant arg); + + private: + QVariant modelName; +}; + +#endif // CONFIGCOMBOBOX_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp new file mode 100644 index 0000000..62ed4f8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp @@ -0,0 +1,36 @@ +#include "configradiobutton.h" + +ConfigRadioButton::ConfigRadioButton(QWidget* parent) : + QRadioButton(parent) +{ + connect(this, SIGNAL(toggled(bool)), this, SLOT(handleToggled(bool))); +} + +QVariant ConfigRadioButton::getAssignedValue() const +{ + return assignedValue; +} + +void ConfigRadioButton::setAssignedValue(const QVariant& value) +{ + assignedValue = value; +} + +void ConfigRadioButton::handleToggled(bool checked) +{ + if (handlingSlot) + return; + + if (checked) + emit toggledOn(assignedValue); + else + emit toggledOff(assignedValue); +} + +void ConfigRadioButton::alignToValue(const QVariant& value) +{ + handlingSlot = true; + setChecked(value == assignedValue); + handlingSlot = false; +} + diff --git a/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h new file mode 100644 index 0000000..b972c26 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h @@ -0,0 +1,46 @@ +#ifndef CONFIGRADIOBUTTON_H +#define CONFIGRADIOBUTTON_H + +#include "guiSQLiteStudio_global.h" +#include <QRadioButton> +#include <QVariant> + +/** + * @brief Config-oriented radio button. + * + * It's just like a usual QRadioButton, except it has a value assigned to it + * and when the radio is toggled on, the signal is emitted to inform about it. + * To inform about the button being toggled off a different signal is emitted. + * It also has a slot to be called when the associated property in the application + * has changed and needs to be reflected in the button - the button checks + * if the value of the property reflects the button's assigned value + * and toggles on or off approprietly. In that case the signals are not emitted. + */ +class GUI_API_EXPORT ConfigRadioButton : public QRadioButton +{ + Q_OBJECT + + Q_PROPERTY(QVariant assignedValue READ getAssignedValue WRITE setAssignedValue) + + public: + explicit ConfigRadioButton(QWidget *parent = 0); + + QVariant getAssignedValue() const; + void setAssignedValue(const QVariant& value); + + private: + QVariant assignedValue; + bool handlingSlot = false; + + signals: + void toggledOn(const QVariant& assignedBalue); + void toggledOff(const QVariant& assignedBalue); + + private slots: + void handleToggled(bool checked); + + public slots: + void alignToValue(const QVariant& value); +}; + +#endif // CONFIGRADIOBUTTON_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp new file mode 100644 index 0000000..655a9aa --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp @@ -0,0 +1,138 @@ +#include "datawidgetmapper.h" +#include <QAbstractItemModel> +#include <QWidget> + +DataWidgetMapper::DataWidgetMapper(QObject *parent) : + QObject(parent) +{ +} +QAbstractItemModel* DataWidgetMapper::getModel() const +{ + return model; +} + +void DataWidgetMapper::setModel(QAbstractItemModel* value) +{ + model = value; +} + +void DataWidgetMapper::addMapping(QWidget* widget, int modelColumn, const QString& propertyName) +{ + MappingEntry* entry = new MappingEntry; + entry->columnIndex = modelColumn; + entry->widget = widget; + entry->propertyName = propertyName; + mappings[widget] = entry; +} + +void DataWidgetMapper::clearMapping() +{ + for (MappingEntry* entry : mappings.values()) + delete entry; + + mappings.clear(); +} + +int DataWidgetMapper::getCurrentIndex() const +{ + return currentIndex; +} + +int DataWidgetMapper::mappedSection(QWidget* widget) const +{ + if (mappings.contains(widget)) + return mappings[widget]->columnIndex; + + return -1; +} + +void DataWidgetMapper::loadFromModel() +{ + QModelIndex idx; + QVariant data; + for (MappingEntry* entry : mappings.values()) + { + idx = model->index(currentIndex, entry->columnIndex); + data = model->data(idx, Qt::EditRole); + entry->widget->setProperty(entry->propertyName.toLatin1().constData(), data); + } +} + +DataWidgetMapper::SubmitFilter DataWidgetMapper::getSubmitFilter() const +{ + return submitFilter; +} + +void DataWidgetMapper::setSubmitFilter(const SubmitFilter& value) +{ + submitFilter = value; +} + +void DataWidgetMapper::setCurrentIndex(int rowIndex) +{ + if (!model) + return; + + if (rowIndex < 0) + return; + + if (rowIndex >= model->rowCount()) + return; + + if (model->rowCount() == 0) + return; + + currentIndex = rowIndex; + loadFromModel(); + emit currentIndexChanged(rowIndex); +} + +void DataWidgetMapper::toFirst() +{ + setCurrentIndex(0); +} + +void DataWidgetMapper::toLast() +{ + if (!model) + return; + + setCurrentIndex(model->rowCount() - 1); +} + +void DataWidgetMapper::toNext() +{ + setCurrentIndex(currentIndex + 1); +} + +void DataWidgetMapper::toPrevious() +{ + setCurrentIndex(currentIndex - 1); +} + +void DataWidgetMapper::submit() +{ + QModelIndex idx; + QVariant value; + for (MappingEntry* entry : mappings.values()) + { + if (submitFilter && !submitFilter(entry->widget)) + continue; + + idx = model->index(currentIndex, entry->columnIndex); + value = entry->widget->property(entry->propertyName.toLatin1().constData()); + model->setData(idx, value, Qt::EditRole); + } +} + +void DataWidgetMapper::revert() +{ + if (!model) + return; + + if (currentIndex < 0) + return; + + loadFromModel(); +} + diff --git a/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h new file mode 100644 index 0000000..4df7d5e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h @@ -0,0 +1,54 @@ +#ifndef DATAWIDGETMAPPER_H +#define DATAWIDGETMAPPER_H + +#include <QObject> +#include <QHash> + +class QAbstractItemModel; + +class DataWidgetMapper : public QObject +{ + Q_OBJECT + public: + typedef std::function<bool(QWidget*)> SubmitFilter; + + explicit DataWidgetMapper(QObject *parent = 0); + + QAbstractItemModel* getModel() const; + void setModel(QAbstractItemModel* value); + void addMapping(QWidget* widget, int modelColumn, const QString& propertyName); + void clearMapping(); + int getCurrentIndex() const; + int mappedSection(QWidget* widget) const; + SubmitFilter getSubmitFilter() const; + void setSubmitFilter(const SubmitFilter& value); + + private: + struct MappingEntry + { + QWidget* widget = nullptr; + int columnIndex = 0; + QString propertyName; + }; + + void loadFromModel(); + + QAbstractItemModel* model = nullptr; + int currentIndex = -1; + QHash<QWidget*,MappingEntry*> mappings; + SubmitFilter submitFilter = nullptr; + + public slots: + void setCurrentIndex(int rowIndex); + void toFirst(); + void toLast(); + void toNext(); + void toPrevious(); + void submit(); + void revert(); + + signals: + void currentIndexChanged(int newRowIndex); +}; + +#endif // DATAWIDGETMAPPER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp new file mode 100644 index 0000000..3b09c79 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp @@ -0,0 +1,30 @@ +#include "extaction.h" +#include <QDebug> +#include <QShortcutEvent> + +ExtAction::ExtAction(QObject *parent) : + QAction(parent) +{ +} + +ExtAction::ExtAction(const QString& text, QObject* parent) : + QAction(text, parent) +{ +} + +ExtAction::ExtAction(const QIcon& icon, const QString& text, QObject* parent) : + QAction(icon, text, parent) +{ +} + +bool ExtAction::event(QEvent* e) +{ + // This implementation code comes mostly from Qt 5.1.0, + // but it was modified to handle ambiguous shortcuts. + if (e->type() == QEvent::Shortcut) + { + activate(Trigger); + return true; + } + return QObject::event(e); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extaction.h b/SQLiteStudio3/guiSQLiteStudio/common/extaction.h new file mode 100644 index 0000000..5530271 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extaction.h @@ -0,0 +1,20 @@ +#ifndef extaction_H +#define extaction_H + +#include "guiSQLiteStudio_global.h" +#include <QAction> + +class GUI_API_EXPORT ExtAction : public QAction +{ + Q_OBJECT + + public: + explicit ExtAction(QObject *parent = 0); + ExtAction(const QString& text, QObject* parent = 0); + ExtAction(const QIcon& icon, const QString& text, QObject* parent = 0); + + protected: + bool event(QEvent* e); +}; + +#endif // extaction_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp new file mode 100644 index 0000000..15bf926 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp @@ -0,0 +1,260 @@ +#include "extactioncontainer.h" +#include "iconmanager.h" +#include "common/extaction.h" +#include "common/global.h" +#include <QSignalMapper> +#include <QToolButton> +#include <QToolBar> +#include <QMenu> +#include <QDebug> + +ExtActionContainer::ClassNameToToolBarAndAction ExtActionContainer::extraActions; +QList<ExtActionContainer*> ExtActionContainer::instances; + +ExtActionContainer::ExtActionContainer() +{ + actionIdMapper = new QSignalMapper(); + + // We need to explicitly cast QSignalMapper::mapped to tell which overloaded version of function we want + QObject::connect(actionIdMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), + [=](int action) {refreshShortcut(action);}); + instances << this; +} + +ExtActionContainer::~ExtActionContainer() +{ + deleteActions(); + safe_delete(actionIdMapper); + instances.removeOne(this); +} + +void ExtActionContainer::initActions() +{ + createActions(); + setupDefShortcuts(); + refreshShortcuts(); + handleExtraActions(); +} + +void ExtActionContainer::createAction(int action, const Icon& icon, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner) +{ + QAction* qAction = new ExtAction(icon, text); + createAction(action, qAction, receiver, slot, container, owner); +} + +void ExtActionContainer::createAction(int action, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner) +{ + QAction* qAction = new ExtAction(text); + createAction(action, qAction, receiver, slot, container, owner); +} + +void ExtActionContainer::bindShortcutsToEnum(CfgCategory &cfgCategory, const QMetaEnum &actionsEnum) +{ + QHash<QString, CfgEntry *>& cfgEntries = cfgCategory.getEntries(); + QString enumName; + CfgStringEntry* stringEntry = nullptr; + for (int i = 0, total = actionsEnum.keyCount(); i < total; ++i) + { + enumName = QString::fromLatin1(actionsEnum.key(i)); + if (!cfgEntries.contains(enumName)) + continue; + + stringEntry = dynamic_cast<CfgStringEntry*>(cfgEntries[enumName]); + if (!stringEntry) + { + qDebug() << "Tried to bind key sequence config entry, but its type was not QString. Ignoring entry:" << cfgEntries[enumName]->getFullKey(); + continue; + } + + defShortcut(actionsEnum.value(i), stringEntry); + } +} + +void ExtActionContainer::defShortcut(int action, CfgStringEntry *cfgEntry) +{ + shortcuts[action] = cfgEntry; + + actionIdMapper->setMapping(cfgEntry, action); + QObject::connect(cfgEntry, SIGNAL(changed(QVariant)), actionIdMapper, SLOT(map())); +} + +void ExtActionContainer::setShortcutContext(const QList<qint32> actions, Qt::ShortcutContext context) +{ + foreach (qint32 act, actions) + actionMap[act]->setShortcutContext(context); +} + +void ExtActionContainer::attachActionInMenu(int parentAction, int childAction, QToolBar* toolbar) +{ + attachActionInMenu(parentAction, actionMap[childAction], toolbar); +} + +void ExtActionContainer::attachActionInMenu(int parentAction, QAction* childAction, QToolBar* toolbar) +{ + attachActionInMenu(actionMap[parentAction], childAction, toolbar); +} + +void ExtActionContainer::attachActionInMenu(QAction* parentAction, QAction* childAction, QToolBar* toolbar) +{ + QToolButton* button = dynamic_cast<QToolButton*>(toolbar->widgetForAction(parentAction)); + QMenu* menu = button->menu(); + + if (!menu) + { + menu = new QMenu(button); + button->setMenu(menu); + button->setPopupMode(QToolButton::MenuButtonPopup); + } + + menu->addAction(childAction); +} + +void ExtActionContainer::updateShortcutTips() +{ +} + +void ExtActionContainer::createAction(int action, QAction* qAction, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner) +{ + if (!owner) + owner = container; + else + owner->addAction(qAction); + + qAction->setParent(owner); + actionMap[action] = qAction; + QObject::connect(qAction, SIGNAL(triggered()), receiver, slot); + container->addAction(qAction); +} + +void ExtActionContainer::deleteActions() +{ + foreach (QAction* action, actionMap.values()) + delete action; + + actionMap.clear(); +} + +void ExtActionContainer::refreshShortcuts() +{ + foreach (int action, actionMap.keys()) + { + if (!shortcuts.contains(action)) + continue; + + if (noConfigShortcutActions.contains(action)) + continue; + + refreshShortcut(action); + } +} + +void ExtActionContainer::refreshShortcut(int action) +{ + QKeySequence seq(shortcuts[action]->get()); + QString txt = seq.toString(QKeySequence::NativeText); + actionMap[action]->setShortcut(seq); + actionMap[action]->setToolTip(actionMap[action]->text() + QString(" (%1)").arg(txt)); +} + +QAction* ExtActionContainer::getAction(int action) +{ + if (!actionMap.contains(action)) + return nullptr; + + return actionMap.value(action); +} + +void ExtActionContainer::handleActionInsert(int toolbar, ActionDetails* details) +{ + if (details->position > -1 && !actionMap.contains(details->position)) + { + qWarning() << "Tried to insert action" << details->action->text() << "before action" << details->position + << "which is not present in action container:" << metaObject()->className(); + return; + } + + QToolBar* toolBar = getToolBar(toolbar); + if (!toolBar) + { + qWarning() << "Tried to insert action" << details->action->text() << ", but toolbar was incorrect: " << toolbar + << "or there is no toolbar in action container:" << metaObject()->className(); + return; + } + + QAction* beforeQAction = actionMap[details->position]; + if (details->after) + { + QList<QAction*> acts = toolBar->actions(); + int idx = acts.indexOf(beforeQAction); + idx++; + if (idx > 0 && idx < acts.size()) + beforeQAction = acts[idx]; + else + beforeQAction = nullptr; + } + + QAction* action = details->action->create(); + toolBar->insertAction(beforeQAction, action); + + ToolbarAndProto toolbarAndProto(toolbar, details); + extraActionToToolbarAndProto[action] = toolbarAndProto; + toolbarAndProtoToAction[toolbarAndProto] = action; + + QObject::connect(action, &QAction::triggered, [this, details, toolbar]() + { + details->action->emitTriggered(this, toolbar); + }); + + details->action->emitInsertedTo(this, toolbar, action); +} + +void ExtActionContainer::handleActionRemoval(int toolbar, ActionDetails* details) +{ + QToolBar* toolBar = getToolBar(toolbar); + if (!toolBar) + { + qWarning() << "Tried to remove action" << details->action->text() << ", but toolbar was incorrect: " << toolbar << "or there is no toolbar in action container:" + << metaObject()->className(); + return; + } + + + ToolbarAndProto toolbarAndProto(toolbar, details); + QAction* action = toolbarAndProtoToAction[toolbarAndProto]; + + details->action->emitAboutToRemoveFrom(this, toolbar, action); + + toolBar->removeAction(action); + extraActionToToolbarAndProto.remove(action); + toolbarAndProtoToAction.remove(toolbarAndProto); + + details->action->emitRemovedFrom(this, toolbar, action); + delete action; +} + +void ExtActionContainer::handleExtraActions() +{ + QString clsName = metaObject()->className(); + if (!extraActions.contains(clsName)) + return; + + // For each toolbar + for (int toolbarId : extraActions[clsName].keys()) + { + // For each action for this toolbar + for (ActionDetails* actionDetails : extraActions[clsName][toolbarId]) + { + // Insert action into toolbar, before action's assigned "before" action + handleActionInsert(toolbarId, actionDetails); + } + } +} + +ExtActionContainer::ActionDetails::ActionDetails() +{ +} + +ExtActionContainer::ActionDetails::ActionDetails(ExtActionPrototype* action, int position, bool after) : + action(action), position(position), after(after) +{ +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h new file mode 100644 index 0000000..808af3e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h @@ -0,0 +1,254 @@ +#ifndef extactionCONTAINER_H +#define extactionCONTAINER_H + +#include "iconmanager.h" +#include "config_builder.h" +#include "extactionprototype.h" +#include <QString> +#include <QHash> +#include <QSet> +#include <QKeySequence> +#include <QShortcut> +#include <QMetaEnum> + +class QAction; +class QObject; +class QWidget; +class QActionGroup; +class QToolBar; +class QSignalMapper; + +#define CFG_SHORTCUTS_METANAME "Shortcuts" + +#define CFG_KEY_LIST(Type, Title, Entries) \ + _CFG_CATEGORIES_WITH_METANAME(Shortcuts##Type, \ + _CFG_CATEGORY_WITH_TITLE(ShortcutsCategory##Type, Entries, Title), \ + CFG_SHORTCUTS_METANAME\ + ) + +#define CFG_KEY_ENTRY(Name, KeyStr, Title) CFG_ENTRY(QString, Name, QKeySequence(KeyStr).toString(), Title) + +#define CFG_KEYS_DEFINE(Type) CFG_DEFINE_LAZY(Shortcuts##Type) + +/** + * @def Declares access object for defined shortuts. + * + * This is the same as CFG_INSTANCE for regular config values. + * It's optional. It doesn't need to be declared, but if you want to refer + * to keys as to configuration values, then you will need this. + */ +#define CFG_KEYS_INSTANCE(Type) (*Cfg::getShortcuts##Type##Instance()) + +/** + * @def Binds shortcuts configuration with actions enumerator. + * @param Type Shortcuts category type that was passed to CFG_KEY_LIST. + * @param EnumName Enumerator type which lists actions that you want bind shortcuts to. + * + * Names of shortcut entries have to match names of enumerator literals in order to bind shortcuts + * to proper actions. + */ +#define BIND_SHORTCUTS(Type, EnumName) \ + for (int _enumCounter = 0, _totalEnums = staticMetaObject.enumeratorCount(); _enumCounter < _totalEnums; _enumCounter++) \ + { \ + if (QString::fromLatin1(staticMetaObject.enumerator(_enumCounter).name()) == #EnumName) \ + { \ + bindShortcutsToEnum(Cfg::getShortcuts##Type##Instance()->ShortcutsCategory##Type, staticMetaObject.enumerator(_enumCounter)); \ + break; \ + } \ + } + +#define GET_SHORTCUTS(Type) ExtActionContainer::getAllShortcutSequences(Cfg::getShortcuts##Type##Instance()->ShortcutsCategory##Type) + +class GUI_API_EXPORT ExtActionContainer +{ + private: + struct GUI_API_EXPORT ActionDetails + { + ActionDetails(); + ActionDetails(ExtActionPrototype* action, int position, bool after); + + ExtActionPrototype* action = nullptr; + int position = -1; + bool after = false; + }; + + typedef QList<ActionDetails*> ExtraActions; + typedef QHash<int,ExtraActions> ToolBarToAction; + typedef QHash<QString,ToolBarToAction> ClassNameToToolBarAndAction; + + public: + ExtActionContainer(); + virtual ~ExtActionContainer(); + + QAction* getAction(int action); + virtual const QMetaObject* metaObject() const = 0; + + template <class T> + static void insertAction(ExtActionPrototype* action, int toolbar = -1); + + template <class T> + static void insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar = -1); + + template <class T> + static void insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar = -1); + + template <class T> + static void removeAction(ExtActionPrototype* action, int toolbar = -1); + + protected: + QHash<int,QAction*> actionMap; + QHash<int,CfgStringEntry*> shortcuts; + QSet<int> noConfigShortcutActions; + + virtual void createActions() = 0; + virtual void setupDefShortcuts() = 0; + + void initActions(); + void createAction(int action, const Icon& icon, const QString& text, const QObject* receiver, const char* slot, QWidget* container, + QWidget* owner = 0); + void createAction(int action, const QString& text, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner = 0); + + /** + * @brief Binds config shortcut entries with action enumerator. + * @param cfgCategory Config category with QString entries that have shortcut definitions. + * @param actionsEnum Enumerator with actions. + * + * Binds shortcuts defined in given config category to actions listed by the enumerator. + * Binding is done by name, that is name of the config entry (in the category) is matched against enumeration name, + * + * You don't normally use this method, but instead use BIND_SHORTCUTS. + */ + void bindShortcutsToEnum(CfgCategory &cfgCategory, const QMetaEnum& actionsEnum); + void defShortcut(int action, CfgStringEntry* cfgEntry); + void setShortcutContext(const QList<qint32> actions, Qt::ShortcutContext context); + + /** + * @brief attachActionInMenu + * @param parentAction Action that will have a submenu. Must already exist. + * @param childAction Action to add to the submenu. Must already exist. + * @param toolbar Toolbar that parentAction is already added to. + * Puts childAction into submenu of parentAction. + */ + void attachActionInMenu(int parentAction, int childAction, QToolBar* toolbar); + void attachActionInMenu(int parentAction, QAction* childAction, QToolBar* toolbar); + void attachActionInMenu(QAction* parentAction, QAction* childAction, QToolBar* toolbar); + void updateShortcutTips(); + + /** + * @brief Tells the toolbar object for given toolbar enum value. + * @param toolbar Toolbar enum value for specific implementation of MdiChild. + * @return Toolbar object or null of there's no toolbar for given value, or no toolbar at all. + * + * The \p toolbar argument should be enum value from the specific implementation of MdiChild, + * for example for TableWindow it could be TOOLBAR_GRID_DATA, which refers to grid data tab toolbar. + * + * For classes with no toolbar this function will always return null; + * + * For classes with only one toolbar this method will always return that toolbar, no matter + * if the \p toolbar argument was correct. + * + * For classes with more than one toolbar this method will return proper toolbar objects only + * when the \p toolbar argument was correct, otherwise it returns null (assuming correct implementation + * of this method). + */ + virtual QToolBar* getToolBar(int toolbar) const = 0; + + void handleActionInsert(int toolbar, ActionDetails* details); + void handleActionRemoval(int toolbar, ActionDetails* details); + + private: + typedef QPair<int,ActionDetails*> ToolbarAndProto; + + void refreshShortcuts(); + void refreshShortcut(int action); + void deleteActions(); + void createAction(int action, QAction* qAction, const QObject* receiver, const char* slot, QWidget* container, QWidget* owner); + void handleExtraActions(); + + template <class T> + static QList<T*> getInstances(); + + template <class T> + static void insertAction(ExtActionPrototype* action, int pos, bool after, int toolbar); + + static ClassNameToToolBarAndAction extraActions; + static QList<ExtActionContainer*> instances; + + QSignalMapper* actionIdMapper = nullptr; + QHash<QAction*,ToolbarAndProto> extraActionToToolbarAndProto; + QHash<ToolbarAndProto,QAction*> toolbarAndProtoToAction; +}; + +template <class T> +void ExtActionContainer::insertAction(ExtActionPrototype* action, int pos, bool after, int toolbar) +{ + ActionDetails* dets = new ActionDetails(action, pos, after); + QString clsName = T::staticMetaObject.className(); + extraActions[clsName][toolbar] << dets; + for (T* instance : getInstances<T>()) + instance->handleActionInsert(toolbar, dets); +} + +template <class T> +void ExtActionContainer::insertAction(ExtActionPrototype* action, int toolbar) +{ + insertAction<T>(action, -1, false, toolbar); +} + +template <class T> +void ExtActionContainer::insertActionAfter(ExtActionPrototype* action, int afterAction, int toolbar) +{ + insertAction<T>(action, afterAction, true, toolbar); +} + +template <class T> +void ExtActionContainer::insertActionBefore(ExtActionPrototype* action, int beforeAction, int toolbar) +{ + insertAction<T>(action, beforeAction, false, toolbar); +} + +template <class T> +void ExtActionContainer::removeAction(ExtActionPrototype* action, int toolbar) +{ + QString clsName = T::staticMetaObject.className(); + if (!extraActions.contains(clsName)) + return; + + if (!extraActions[clsName].contains(toolbar)) + return; + + ActionDetails* dets = nullptr; + for (ActionDetails* d : extraActions[clsName][toolbar]) + { + if (d->action == action) + { + dets = d; + break; + } + } + + if (!dets) + return; + + for (T* instance : getInstances<T>()) + instance->handleActionRemoval(toolbar, dets); + + extraActions[clsName][toolbar].removeOne(dets); + delete dets; +} + +template <class T> +QList<T*> ExtActionContainer::getInstances() +{ + QList<T*> typedInstances; + T* typedInstance = nullptr; + for (ExtActionContainer* instance : instances) + { + typedInstance = dynamic_cast<T*>(instance); + if (typedInstance) + typedInstances << typedInstance; + } + return typedInstances; +} + +#endif // extactionCONTAINER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp new file mode 100644 index 0000000..a6756b0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp @@ -0,0 +1,19 @@ +#include "extactionmanagementnotifier.h" +#include "extactioncontainer.h" +#include <QDebug> + +ExtActionManagementNotifier::ExtActionManagementNotifier(QAction* action) : + QObject(nullptr), action(action) +{ + qDebug() << "create notifier" << this; +} + +void ExtActionManagementNotifier::inserted(ExtActionContainer* object, QToolBar* toolbar) +{ + emit actionInserted(object, toolbar, action); +} + +void ExtActionManagementNotifier::removed(ExtActionContainer* object, QToolBar* toolbar) +{ + emit actionRemoved(object, toolbar, action); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h new file mode 100644 index 0000000..5ecb4e0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h @@ -0,0 +1,30 @@ +#ifndef EXTACTIONMANAGEMENTNOTIFIER_H +#define EXTACTIONMANAGEMENTNOTIFIER_H + +#include <QObject> +#include <QSharedPointer> + +class QToolBar; +class QAction; +class ExtActionContainer; + +class ExtActionManagementNotifier : public QObject +{ + Q_OBJECT + public: + explicit ExtActionManagementNotifier(QAction* action); + + void inserted(ExtActionContainer* object, QToolBar* toolbar); + void removed(ExtActionContainer* object, QToolBar* toolbar); + + private: + QAction* action = nullptr; + + signals: + void actionInserted(ExtActionContainer* object, QToolBar* toolbar, QAction* action); + void actionRemoved(ExtActionContainer* object, QToolBar* toolbar, QAction* action); +}; + +typedef QSharedPointer<ExtActionManagementNotifier> ExtActionManagementNotifierPtr; + +#endif // EXTACTIONMANAGEMENTNOTIFIER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp new file mode 100644 index 0000000..0dfafba --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp @@ -0,0 +1,65 @@ +#include "extactionprototype.h" +#include "extactioncontainer.h" +#include <QAction> +#include <QDebug> + +ExtActionPrototype::ExtActionPrototype(QObject* parent) : + QObject(parent) +{ + separator = true; +} + +ExtActionPrototype::ExtActionPrototype(const QString& text, QObject* parent) : + QObject(parent), actionText(text) +{ +} + +ExtActionPrototype::ExtActionPrototype(const QIcon& icon, const QString& text, QObject* parent) : + QObject(parent), icon(icon), actionText(text) +{ +} + +ExtActionPrototype::~ExtActionPrototype() +{ + +} + +QString ExtActionPrototype::text() const +{ + return actionText; +} + +QAction* ExtActionPrototype::create(QObject* parent) +{ + if (!parent) + parent = this; + + if (separator) + { + QAction* act = new QAction(parent); + act->setSeparator(true); + return act; + } + + return new QAction(icon, actionText, parent); +} + +void ExtActionPrototype::emitInsertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action) +{ + emit insertedTo(actionContainer, toolbar, action); +} + +void ExtActionPrototype::emitAboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action) +{ + emit aboutToRemoveFrom(actionContainer, toolbar, action); +} + +void ExtActionPrototype::emitRemovedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action) +{ + emit removedFrom(actionContainer, toolbar, action); +} + +void ExtActionPrototype::emitTriggered(ExtActionContainer* actionContainer, int toolbar) +{ + emit triggered(actionContainer, toolbar); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h new file mode 100644 index 0000000..7fd20d1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h @@ -0,0 +1,44 @@ +#ifndef EXTACTIONPROTOTYPE_H +#define EXTACTIONPROTOTYPE_H + +#include "guiSQLiteStudio_global.h" +#include <QString> +#include <QIcon> +#include <QObject> + +class QAction; +class ExtActionContainer; + +class GUI_API_EXPORT ExtActionPrototype : public QObject +{ + Q_OBJECT + + friend class ExtActionContainer; + + public: + explicit ExtActionPrototype(QObject* parent); + ExtActionPrototype(const QString& text, QObject* parent = 0); + ExtActionPrototype(const QIcon& icon, const QString& text, QObject* parent = 0); + ~ExtActionPrototype(); + + QString text() const; + QAction* create(QObject* parent = 0); + + private: + void emitInsertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void emitAboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void emitRemovedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void emitTriggered(ExtActionContainer* actionContainer, int toolbar); + + QIcon icon; + QString actionText; + bool separator = false; + + signals: + void insertedTo(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void aboutToRemoveFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void removedFrom(ExtActionContainer* actionContainer, int toolbar, QAction* action); + void triggered(ExtActionContainer* actionContainer, int toolbar); +}; + +#endif // EXTACTIONPROTOTYPE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp new file mode 100644 index 0000000..bd0bffa --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp @@ -0,0 +1,118 @@ +#include "extlineedit.h" +#include "iconmanager.h" +#include <QStyle> +#include <QAction> +#include <QDebug> + +ExtLineEdit::ExtLineEdit(QWidget* parent) + : QLineEdit(parent) +{ + init(); +} + +ExtLineEdit::ExtLineEdit(const QString& text, QWidget *parent) + : QLineEdit(text, parent) +{ + init(); +} + +void ExtLineEdit::init() +{ + connect(this, &QLineEdit::textChanged, this, &ExtLineEdit::handleTextChanged); +} + +void ExtLineEdit::updateMinSize() +{ + setMinimumSize(expandingMinWidth, 0); +} + +int ExtLineEdit::getExpandingMaxWidth() const +{ + return expandingMaxWidth; +} + +void ExtLineEdit::setExpandingMaxWidth(int value) +{ + expandingMaxWidth = value; + setMaximumWidth(value); +} + +void ExtLineEdit::setClearButtonEnabled(bool enable) +{ + QLineEdit::setClearButtonEnabled(enable); + if (enable) + { + // This is a hack to get to know when QLineEdit's clear button is pressed. + // Unfortunately Qt 5.2 API doesn't provide such information, + // but we can find QAction responsible for it by its object name + // and handle its triggered() signal. + // This is not part of an official Qt's API and may be modified in any Qt version. + // Ugly, but works. + static const char* qtClearBtnActionName = "_q_qlineeditclearaction"; + QAction *clearAction = findChild<QAction*>(qtClearBtnActionName); + if (!clearAction) + { + qWarning() << "Could not find 'clear action' in QLineEdit, so 'valueErased()' signal won't be emitted from ExtLineEdit."; + return; + } + connect(clearAction, SIGNAL(triggered()), this, SIGNAL(valueErased())); + } +} + + +bool ExtLineEdit::getExpanding() const +{ + return expanding; +} + +void ExtLineEdit::setExpanding(bool value) +{ + expanding = value; + if (!expanding) + setFixedWidth(-1); + else + setFixedWidth(expandingMinWidth); +} + +int ExtLineEdit::getExpandingMinWidth() const +{ + return expandingMinWidth; +} + +void ExtLineEdit::setExpandingMinWidth(int value) +{ + expandingMinWidth = value; + updateMinSize(); +} + +void ExtLineEdit::handleTextChanged() +{ + QString txt = text(); + if (!expanding) + return; + + // Text width + int newWidth = fontMetrics().width(txt); + + // Text margins + QMargins margins = textMargins(); + newWidth += margins.left() + margins.right(); + + // Content margins + QMargins localContentsMargins = contentsMargins(); + newWidth += localContentsMargins.left() + localContentsMargins.right(); + + // Frame + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + newWidth += frameWidth * 2; + + // Extra space + newWidth += expandingExtraSpace; + + if (newWidth < expandingMinWidth) + newWidth = expandingMinWidth; + else if (expandingMaxWidth > 0 && newWidth > expandingMaxWidth) + newWidth = expandingMaxWidth; + + setFixedWidth(newWidth); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h new file mode 100644 index 0000000..b3bffbb --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h @@ -0,0 +1,45 @@ +#ifndef EXTLINEEDIT_H +#define EXTLINEEDIT_H + +#include "guiSQLiteStudio_global.h" +#include <QLineEdit> + +class QToolButton; + +class GUI_API_EXPORT ExtLineEdit : public QLineEdit +{ + Q_OBJECT + + public: + explicit ExtLineEdit(QWidget *parent = 0); + explicit ExtLineEdit(const QString& text, QWidget *parent = 0); + + bool getExpanding() const; + void setExpanding(bool value); + + int getExpandingMinWidth() const; + void setExpandingMinWidth(int value); + + int getExpandingMaxWidth() const; + void setExpandingMaxWidth(int value); + + void setClearButtonEnabled(bool enable); + + private: + void init(); + void updateMinSize(); + + static const int expandingExtraSpace = 4; // QLineEdit has hardcoded horizontal margin of 2 for both sides + + bool expanding = false; + int expandingMinWidth = 0; + int expandingMaxWidth = -1; + + private slots: + void handleTextChanged(); + + signals: + void valueErased(); +}; + +#endif // EXTLINEEDIT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp new file mode 100644 index 0000000..9f628ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp @@ -0,0 +1,98 @@ +#include "fileedit.h" +#include "iconmanager.h" +#include "uiconfig.h" +#include <QHBoxLayout> +#include <QLineEdit> +#include <QToolButton> +#include <QFileDialog> + +FileEdit::FileEdit(QWidget *parent) : + QWidget(parent) +{ + setLayout(new QHBoxLayout()); + layout()->setMargin(0); + + lineEdit = new QLineEdit(); + button = new QToolButton(); + button->setIcon(ICONS.OPEN_FILE); + layout()->addWidget(lineEdit); + layout()->addWidget(button); + + connect(button, SIGNAL(clicked()), this, SLOT(browse())); + connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(lineTextChanged())); +} + +QString FileEdit::getFile() const +{ + return file; +} + +bool FileEdit::getSave() const +{ + return save; +} + +QString FileEdit::getDialogTitle() const +{ + return dialogTitle; +} + +QString FileEdit::getFilters() const +{ + return filters; +} + +void FileEdit::browse() +{ + QString path; + QString dir = getFileDialogInitPath(); + if (save) + path = QFileDialog::getSaveFileName(this, dialogTitle, dir, filters); + else + path = QFileDialog::getOpenFileName(this, dialogTitle, dir, filters); + + if (path.isNull()) + return; + + setFile(path); + setFileDialogInitPathByFile(path); +} + +void FileEdit::lineTextChanged() +{ + file = lineEdit->text(); + emit fileChanged(file); +} + +void FileEdit::setFile(QString arg) +{ + if (file != arg) { + file = arg; + lineEdit->setText(file); + emit fileChanged(arg); + } +} + +void FileEdit::setSave(bool arg) +{ + if (save != arg) { + save = arg; + emit saveChanged(arg); + } +} + +void FileEdit::setDialogTitle(QString arg) +{ + if (dialogTitle != arg) { + dialogTitle = arg; + emit dialogTitleChanged(arg); + } +} + +void FileEdit::setFilters(QString arg) +{ + if (filters != arg) { + filters = arg; + emit filtersChanged(arg); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h new file mode 100644 index 0000000..8cb62a6 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/fileedit.h @@ -0,0 +1,52 @@ +#ifndef FILEEDIT_H +#define FILEEDIT_H + +#include "guiSQLiteStudio_global.h" +#include <QWidget> + +class QLineEdit; +class QToolButton; + +class GUI_API_EXPORT FileEdit : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QString file READ getFile WRITE setFile NOTIFY fileChanged) + Q_PROPERTY(bool save READ getSave WRITE setSave NOTIFY saveChanged) + Q_PROPERTY(QString dialogTitle READ getDialogTitle WRITE setDialogTitle NOTIFY dialogTitleChanged) + Q_PROPERTY(QString filters READ getFilters WRITE setFilters NOTIFY filtersChanged) + + public: + explicit FileEdit(QWidget *parent = 0); + + QString getFile() const; + bool getSave() const; + QString getDialogTitle() const; + QString getFilters() const; + + private: + QString file; + bool save = false; + QString dialogTitle; + QString filters; + QLineEdit* lineEdit = nullptr; + QToolButton* button = nullptr; + + signals: + void fileChanged(QString arg); + void saveChanged(bool arg); + void dialogTitleChanged(QString arg); + void filtersChanged(QString arg); + + private slots: + void browse(); + void lineTextChanged(); + + public slots: + void setFile(QString arg); + void setSave(bool arg); + void setDialogTitle(QString arg); + void setFilters(QString arg); +}; + +#endif // FILEEDIT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp new file mode 100644 index 0000000..a70122b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp @@ -0,0 +1,68 @@ +#include "fontedit.h" +#include "ui_fontedit.h" +#include "iconmanager.h" +#include <QDebug> +#include <QFontDialog> + +FontEdit::FontEdit(QWidget *parent) : + QWidget(parent), + ui(new Ui::FontEdit) +{ + init(); +} + +FontEdit::~FontEdit() +{ + delete ui; +} + +QFont FontEdit::getFont() const +{ + return font; +} + +void FontEdit::setFont(QFont arg) +{ + font = arg; + updateFont(); +} + +void FontEdit::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void FontEdit::init() +{ + ui->setupUi(this); + ui->button->setIcon(ICONS.FONT_BROWSE); + connect(ui->button, SIGNAL(clicked()), this, SLOT(browse())); + updateFont(); +} + +void FontEdit::updateFont() +{ + static const QString text = "%1, %2"; + ui->label->setFont(font); + int size = font.pointSize() > -1 ? font.pointSize() : font.pixelSize(); + ui->label->setText(text.arg(font.family()).arg(size)); +} + +void FontEdit::browse() +{ + bool ok; + QFont newFont = QFontDialog::getFont(&ok, ui->label->font(), this, tr("Choose font", "font configuration")); + if (!ok) + return; + + font = newFont; + updateFont(); + emit fontChanged(newFont); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h new file mode 100644 index 0000000..e68463b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.h @@ -0,0 +1,43 @@ +#ifndef FONTEDIT_H +#define FONTEDIT_H + +#include "guiSQLiteStudio_global.h" +#include <QWidget> + +namespace Ui { + class FontEdit; +} + +class GUI_API_EXPORT FontEdit : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QFont font READ getFont WRITE setFont NOTIFY fontChanged) + + public: + explicit FontEdit(QWidget *parent = 0); + ~FontEdit(); + + QFont getFont() const; + + public slots: + void setFont(QFont arg); + + protected: + void changeEvent(QEvent *e); + + private: + void init(); + void updateFont(); + + Ui::FontEdit *ui = nullptr; + QFont font; + + private slots: + void browse(); + + signals: + void fontChanged(const QFont& font); +}; + +#endif // FONTEDIT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui new file mode 100644 index 0000000..d8daa9f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FontEdit</class> + <widget class="QWidget" name="FontEdit"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>451</width> + <height>35</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp new file mode 100644 index 0000000..bfcf7b0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp @@ -0,0 +1,38 @@ +#include "intvalidator.h" + +IntValidator::IntValidator(QObject *parent) : + QIntValidator(parent) +{ +} + +IntValidator::IntValidator(int min, int max, QObject* parent) + : QIntValidator(min, max, parent) +{ +} + +void IntValidator::fixup(QString& input) const +{ + QIntValidator::fixup(input); + if (input.trimmed().isEmpty()) + input = QString::number(defaultValue); + + bool ok; + int val = input.toInt(&ok); + if (!ok) + return; + + if (val < bottom()) + input = QString::number(bottom()); + else if (val > top()) + input = QString::number(top()); +} + +int IntValidator::getDefaultValue() const +{ + return defaultValue; +} + +void IntValidator::setDefaultValue(int value) +{ + defaultValue = value; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h new file mode 100644 index 0000000..5d2edaa --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h @@ -0,0 +1,25 @@ +#ifndef INTVALIDATOR_H +#define INTVALIDATOR_H + +#include "guiSQLiteStudio_global.h" +#include <QIntValidator> +#include <QString> + +class GUI_API_EXPORT IntValidator : public QIntValidator +{ + Q_OBJECT + + public: + explicit IntValidator(QObject *parent = 0); + explicit IntValidator(int min, int max, QObject *parent = 0); + + void fixup(QString& input) const; + + int getDefaultValue() const; + void setDefaultValue(int value); + + private: + int defaultValue = 0; +}; + +#endif // INTVALIDATOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp new file mode 100644 index 0000000..5a48033 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp @@ -0,0 +1,152 @@ +#include "numericspinbox.h" +#include "common/unused.h" +#include <QLineEdit> +#include <QVariant> +#include <QDebug> + +NumericSpinBox::NumericSpinBox(QWidget *parent) : + QAbstractSpinBox(parent) +{ + connect(lineEdit(), &QLineEdit::textChanged, this, &NumericSpinBox::valueEdited); +} + +void NumericSpinBox::stepBy(int steps) +{ + if (isReadOnly()) + return; + + switch (value.userType()) + { + case QVariant::Double: + stepDoubleBy(steps); + break; + case QVariant::Int: + case QVariant::LongLong: + stepIntBy(steps); + break; + default: + break; + } + updateText(); +} + +QValidator::State NumericSpinBox::validate(QString& input, int& pos) const +{ + UNUSED(input); + UNUSED(pos); + emit modified(); + + if (strict) + return validateStrict(input, pos); + + return QValidator::Acceptable; +} + +void NumericSpinBox::stepIntBy(int steps) +{ + qint64 intVal = value.toLongLong(); + intVal += steps; + value = intVal; + emit modified(); +} + +void NumericSpinBox::stepDoubleBy(int steps) +{ + double doubleVal = value.toDouble(); + doubleVal += steps; + value = doubleVal; + emit modified(); +} + +void NumericSpinBox::updateText() +{ + lineEdit()->setText(value.toString()); +} + +QValidator::State NumericSpinBox::validateStrict(QString& input, int& pos) const +{ + if (input.trimmed().isEmpty()) + return allowEmpty ? QValidator::Acceptable : QValidator::Invalid; + + QIntValidator vint; + if (vint.validate(input, pos) != QValidator::Invalid) + return QValidator::Acceptable; + + QDoubleValidator dint; + if (dint.validate(input, pos) != QValidator::Invalid) + return QValidator::Acceptable; + + return QValidator::Invalid; +} +bool NumericSpinBox::getAllowEmpty() const +{ + return allowEmpty; +} + +void NumericSpinBox::setAllowEmpty(bool value) +{ + allowEmpty = value; +} + + +bool NumericSpinBox::isStrict() const +{ + return strict; +} + +void NumericSpinBox::setStrict(bool value, bool allowEmpty) +{ + strict = value; + this->allowEmpty = allowEmpty; +} + +void NumericSpinBox::valueEdited(const QString& value) +{ + setValueInternal(value); +} + +QAbstractSpinBox::StepEnabled NumericSpinBox::stepEnabled() const +{ + return StepDownEnabled|StepUpEnabled; +} + +QVariant NumericSpinBox::getFixedVariant(const QVariant& value) +{ + bool ok; + qint64 longVal = value.toLongLong(&ok); + if (ok) + return longVal; + + return value.toDouble(); +} + +void NumericSpinBox::setValueInternal(const QVariant& newValue) +{ + switch (newValue.userType()) + { + case QVariant::String: + value = getFixedVariant(newValue); + break; + case QVariant::Double: + case QVariant::Int: + case QVariant::LongLong: + value = newValue; + break; + default: + value = 0; + } +} + +QVariant NumericSpinBox::getValue() const +{ + return value; +} + +void NumericSpinBox::setValue(const QVariant& newValue, bool nullAsZero) +{ + setValueInternal(newValue); + if (!nullAsZero && newValue.isNull()) + value = newValue; + + updateText(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h new file mode 100644 index 0000000..79cd56c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h @@ -0,0 +1,57 @@ +#ifndef QNUMERICSPINBOX_H +#define QNUMERICSPINBOX_H + +#include "guiSQLiteStudio_global.h" +#include <QAbstractSpinBox> + +/** + * @brief The NumericSpinBox class + * This class implements a spinbox for numeric SQLite types. + * This includes integers, as well as decimals. + * User is also allowed to type in any text value (unless "strict" property is set), + * but once he uses "step up" or "step down", the text value + * gets replaced with 0. + * If strict propery is set, also the allowEmpty property starts to matter. + * Otherwise allowEmpty is ignored. + */ +class GUI_API_EXPORT NumericSpinBox : public QAbstractSpinBox +{ + Q_OBJECT + public: + explicit NumericSpinBox(QWidget *parent = 0); + + void stepBy(int steps); + QValidator::State validate(QString& input, int& pos) const; + + QVariant getValue() const; + void setValue(const QVariant& newValue, bool nullAsZero = true); + + bool isStrict() const; + void setStrict(bool value, bool allowEmpty = true); + + bool getAllowEmpty() const; + void setAllowEmpty(bool value); + + protected: + StepEnabled stepEnabled() const; + + private: + QVariant getFixedVariant(const QVariant& value); + void setValueInternal(const QVariant& newValue); + void stepIntBy(int steps); + void stepDoubleBy(int steps); + void updateText(); + QValidator::State validateStrict(QString &input, int &pos) const; + + QVariant value; + bool strict = false; + bool allowEmpty = true; + + private slots: + void valueEdited(const QString& value); + + signals: + void modified() const; +}; + +#endif // QNUMERICSPINBOX_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp new file mode 100644 index 0000000..c9e1446 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp @@ -0,0 +1,49 @@ +#include "tablewidget.h" +#include <QKeyEvent> +#include <QApplication> +#include <QClipboard> +#include <QLabel> + +TableWidget::TableWidget(QWidget *parent) : + QTableWidget(parent) +{ +} + +void TableWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->matches(QKeySequence::Copy)) + { + copy(); + return; + } + + QTableWidget::keyPressEvent(event); +} + +void TableWidget::copy() +{ + QStringList strings; + QStringList cols; + for (int i = 0, total = rowCount(); i < total; ++i) + { + if (!item(i, 0)->isSelected()) + continue; + + for (int c = 1; c <= 2; c++) + { + if (cellWidget(i, c)) + { + QLabel* l = dynamic_cast<QLabel*>(cellWidget(i, c)); + if (l) + cols << l->text(); + } + else + { + cols << item(i, c)->text(); + } + } + strings << cols.join(" "); + } + + QApplication::clipboard()->setText(strings.join("\n")); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h new file mode 100644 index 0000000..26b424c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h @@ -0,0 +1,23 @@ +#ifndef TABLEWIDGET_H +#define TABLEWIDGET_H + +#include "guiSQLiteStudio_global.h" +#include <QTableWidget> + +class QKeyEvent; + +class GUI_API_EXPORT TableWidget : public QTableWidget +{ + Q_OBJECT + public: + explicit TableWidget(QWidget *parent = 0); + + protected: + void keyPressEvent(QKeyEvent *event); + + + public slots: + void copy(); +}; + +#endif // TABLEWIDGET_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp new file mode 100644 index 0000000..48ea46e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp @@ -0,0 +1,33 @@ +#include "userinputfilter.h" +#include "common/unused.h" +#include <QTimer> +#include <QLineEdit> + +UserInputFilter::UserInputFilter(QLineEdit* lineEdit, QObject* filterHandler, const char* handlerSlot) : + QObject(lineEdit), + lineEdit(lineEdit) +{ + timer = new QTimer(this); + timer->setSingleShot(false); + timer->setInterval(200); + connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterModified(QString))); + connect(timer, SIGNAL(timeout()), this, SLOT(applyFilter())); + connect(this, SIGNAL(applyFilter(QString)), filterHandler, handlerSlot); +} + +void UserInputFilter::setDelay(int msecs) +{ + timer->setInterval(msecs); +} + +void UserInputFilter::filterModified(const QString& newValue) +{ + UNUSED(newValue); + timer->start(); +} + +void UserInputFilter::applyFilter() +{ + timer->stop(); + emit applyFilter(lineEdit->text()); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h new file mode 100644 index 0000000..1b6f7ee --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h @@ -0,0 +1,31 @@ +#ifndef USERINPUTFILTER_H +#define USERINPUTFILTER_H + +#include "guiSQLiteStudio_global.h" +#include <QObject> + +class QTimer; +class QLineEdit; + +class GUI_API_EXPORT UserInputFilter : public QObject +{ + Q_OBJECT + + public: + UserInputFilter(QLineEdit* lineEdit, QObject* filterHandler, const char* handlerSlot); + + void setDelay(int msecs); + + private: + QTimer* timer = nullptr; + QLineEdit* lineEdit = nullptr; + + private slots: + void filterModified(const QString& newValue); + void applyFilter(); + + signals: + void applyFilter(const QString& value); +}; + +#endif // USERINPUTFILTER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp new file mode 100644 index 0000000..596b000 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp @@ -0,0 +1,20 @@ +#include "verifiablewizardpage.h" + +VerifiableWizardPage::VerifiableWizardPage(QWidget *parent) : + QWizardPage(parent) +{ +} + +bool VerifiableWizardPage::isComplete() const +{ + if (!validator) + return false; + + return validator(); +} + +void VerifiableWizardPage::setValidator(const Validator& value) +{ + validator = value; +} + diff --git a/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h new file mode 100644 index 0000000..8912258 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h @@ -0,0 +1,22 @@ +#ifndef VERIFIABLEWIZARDPAGE_H +#define VERIFIABLEWIZARDPAGE_H + +#include "guiSQLiteStudio_global.h" +#include <QWizardPage> + +class GUI_API_EXPORT VerifiableWizardPage : public QWizardPage +{ + Q_OBJECT + public: + typedef std::function<bool()> Validator; + + explicit VerifiableWizardPage(QWidget *parent = 0); + + bool isComplete() const; + void setValidator(const Validator& value); + + private: + Validator validator; +}; + +#endif // VERIFIABLEWIZARDPAGE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp new file mode 100644 index 0000000..7cc6a4e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp @@ -0,0 +1,209 @@ +#include "widgetcover.h" +#include "common/unused.h" +#include <QVariantAnimation> +#include <QDebug> +#include <QGraphicsBlurEffect> +#include <QPushButton> +#include <QGridLayout> +#include <QEvent> +#include <QPushButton> +#include <QProgressBar> + +WidgetCover::WidgetCover(QWidget *parent) : + QWidget(parent) +{ + init(); +} + +WidgetCover::WidgetCover(const QEasingCurve& easingCurve, QWidget* parent) + : QWidget(parent), easingCurve(easingCurve) +{ + init(); +} + +WidgetCover::~WidgetCover() +{ + interruptAction(); +} + +void WidgetCover::init() +{ + parentWidget()->installEventFilter(this); + + setLayout(new QGridLayout(this)); + layout()->setAlignment(Qt::AlignCenter); + + container = new QWidget(this); + container->setVisible(false); + layout()->addWidget(container); + + containerLayout = new QGridLayout(container); + containerLayout->setSizeConstraint(QLayout::SetFixedSize); + + animation = new QVariantAnimation(this); + animation->setEasingCurve(easingCurve); + animation->setDuration(duration); + connect(animation, SIGNAL(valueChanged(QVariant)), this, SLOT(animationUpdate(QVariant))); + connect(animation, SIGNAL(finished()), this, SLOT(animationFinished())); + + setAutoFillBackground(true); + resetBackground(); + move(0, 0); + widgetResized(); + hide(); +} + +void WidgetCover::interruptAction() +{ + setVisible(false); + animation->stop(); +} + +void WidgetCover::resetBackground() +{ + QPalette pal = palette(); + pal.setBrush(QPalette::Window, QColor(0, 0, 0, 0)); + setPalette(pal); +} + +void WidgetCover::animationUpdate(const QVariant& value) +{ + QPalette pal = palette(); + pal.setBrush(QPalette::Window, value.value<QColor>()); + setPalette(pal); +} + +void WidgetCover::animationFinished() +{ + switch (actionInProgres) + { + case Action::HIDING: + { + setVisible(false); + resetBackground(); + break; + } + case Action::SHOWING: + { + container->setVisible(true); + break; + } + default: + break; + } + + actionInProgres = Action::NONE; +} + +void WidgetCover::widgetResized() +{ +// qDebug() << parentWidget()->size(); + setFixedSize(parentWidget()->size()); +} + +void WidgetCover::show() +{ + if (actionInProgres == Action::SHOWING) + return; + + if (actionInProgres == Action::HIDING) + animation->stop(); + + actionInProgres = Action::SHOWING; + + if (cancelButton) + cancelButton->setEnabled(true); + + QPalette pal = palette(); + animation->setStartValue(QVariant(pal.brush(QPalette::Window).color())); + animation->setEndValue(QVariant(QColor(0, 0, 0, transparency))); + setVisible(true); + container->setVisible(true); + animation->start(); +} + +void WidgetCover::hide() +{ + if (actionInProgres == Action::HIDING) + return; + + if (actionInProgres == Action::SHOWING) + animation->stop(); + + actionInProgres = Action::HIDING; + + container->setVisible(false); + + QPalette pal = palette(); + animation->setStartValue(QVariant(pal.brush(QPalette::Window).color())); + animation->setEndValue(QVariant(QColor(0, 0, 0, 0))); + animation->start(); +} + +QEasingCurve WidgetCover::getEasingCurve() const +{ + return easingCurve; +} + +void WidgetCover::setEasingCurve(const QEasingCurve& value) +{ + easingCurve = value; + animation->setEasingCurve(easingCurve); +} + +int WidgetCover::getDuration() const +{ + return duration; +} + +void WidgetCover::setDuration(int value) +{ + duration = value; + animation->setDuration(duration); +} + +int WidgetCover::getTransparency() const +{ + return transparency; +} + +void WidgetCover::setTransparency(int value) +{ + if (value < 0) + value = 0; + + if (value > 255) + value = 255; + + transparency = value; +} + +QGridLayout* WidgetCover::getContainerLayout() +{ + return containerLayout; +} + +bool WidgetCover::eventFilter(QObject* obj, QEvent* e) +{ + UNUSED(obj); + if (e->type() == QEvent::Resize) + widgetResized(); + + return false; +} + +void WidgetCover::initWithInterruptContainer(const QString& interruptButtonText) +{ + cancelButton = new QPushButton(); + cancelButton->setText(interruptButtonText.isNull() ? tr("Interrupt") : interruptButtonText); + + busyBar = new QProgressBar(); + busyBar->setRange(0, 0); + busyBar->setTextVisible(false); + + containerLayout->addWidget(busyBar, 0, 0); + containerLayout->addWidget(cancelButton, 1, 0); + + connect(cancelButton, &QPushButton::clicked, [=]() {cancelButton->setEnabled(false);}); + connect(cancelButton, SIGNAL(clicked()), this, SIGNAL(cancelClicked())); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h new file mode 100644 index 0000000..d0ccef7 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h @@ -0,0 +1,72 @@ +#ifndef WIDGETCOVER_H +#define WIDGETCOVER_H + +#include "guiSQLiteStudio_global.h" +#include <QWidget> +#include <QEasingCurve> +#include <QVariant> + +class QVariantAnimation; +class QGridLayout; +class QPushButton; +class QProgressBar; + +class GUI_API_EXPORT WidgetCover : public QWidget +{ + Q_OBJECT + + public: + explicit WidgetCover(QWidget *parent); + explicit WidgetCover(const QEasingCurve& easingCurve, QWidget *parent); + virtual ~WidgetCover(); + + QEasingCurve getEasingCurve() const; + void setEasingCurve(const QEasingCurve& value); + + int getDuration() const; + void setDuration(int value); + + int getTransparency() const; + void setTransparency(int value); + + QGridLayout* getContainerLayout(); + bool eventFilter(QObject* obj, QEvent* e); + + void initWithInterruptContainer(const QString& interruptButtonText = QString()); + + private: + enum class Action + { + SHOWING, + HIDING, + NONE + }; + + void init(); + void interruptAction(); + void resetBackground(); + void widgetResized(); + + Action actionInProgres = Action::NONE; + QVariantAnimation* animation = nullptr; + QEasingCurve easingCurve = QEasingCurve::OutCubic; + int duration = 150; + int transparency = 128; + QWidget* container = nullptr; + QGridLayout* containerLayout = nullptr; + QPushButton* cancelButton = nullptr; + QProgressBar* busyBar = nullptr; + + signals: + void cancelClicked(); + + private slots: + void animationUpdate(const QVariant& value); + void animationFinished(); + + public slots: + void show(); + void hide(); +}; + +#endif // WIDGETCOVER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp new file mode 100644 index 0000000..c6823e4 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp @@ -0,0 +1,412 @@ +#include "widgetstateindicator.h" +#include "iconmanager.h" +#include "common/unused.h" +#include "uiutils.h" +#include "mdichild.h" +#include <QLabel> +#include <QCheckBox> +#include <QGroupBox> +#include <QEvent> +#include <QHBoxLayout> +#include <QGraphicsDropShadowEffect> +#include <QSequentialAnimationGroup> +#include <QPropertyAnimation> +#include <QDebug> +#include <QScrollArea> + +QHash<QWidget*,WidgetStateIndicator*> WidgetStateIndicator::instances; + +WidgetStateIndicator::WidgetStateIndicator(QWidget *widget) : + QObject(widget), widget(widget) +{ + widget->installEventFilter(this); + detectWindowParent(); + initPositionMode(); + initEffects(); + initLabel(); + updateMode(); + updatePosition(); + finalInit(); +} + +WidgetStateIndicator::~WidgetStateIndicator() +{ + instances.remove(widget); + widget->removeEventFilter(this); + windowParent->removeEventFilter(this); +} + +void WidgetStateIndicator::initLabel() +{ + label = new QLabel(); + label->setMargin(0); + label->installEventFilter(this); + label->setGraphicsEffect(highlightingEffect); + + labelParent = new QWidget(windowParent); + labelParent->setLayout(new QHBoxLayout()); + labelParent->layout()->setMargin(0); + labelParent->layout()->addWidget(label); + labelParent->setGraphicsEffect(glowEffect); +} + +void WidgetStateIndicator::initEffects() +{ + initGlowEffects(); + initHighlightingEffects(); + initAnimations(); +} + +void WidgetStateIndicator::initGlowEffects() +{ + glowEffect = new QGraphicsDropShadowEffect(); + glowEffect->setBlurRadius(10.0); + glowEffect->setOffset(0.0); + glowEffect->setEnabled(true); +} + +void WidgetStateIndicator::initHighlightingEffects() +{ + highlightingEffect = new QGraphicsColorizeEffect(); + highlightingEffect->setColor(Qt::white); + highlightingEffect->setStrength(0.3); + highlightingEffect->setEnabled(false); +} + +void WidgetStateIndicator::initAnimations() +{ + animation = new QSequentialAnimationGroup(this); + animation->setLoopCount(-1); + + // Animation of glow efect + QPropertyAnimation* varAnim = new QPropertyAnimation(glowEffect, "blurRadius"); + varAnim->setStartValue(3.0); + varAnim->setEndValue(14.0); + varAnim->setEasingCurve(QEasingCurve::InOutCubic); + varAnim->setDuration(300); + animation->addAnimation(varAnim); + + varAnim = new QPropertyAnimation(glowEffect, "blurRadius"); + varAnim->setStartValue(14.0); + varAnim->setEndValue(3.0); + varAnim->setEasingCurve(QEasingCurve::InOutCubic); + varAnim->setDuration(300); + animation->addAnimation(varAnim); +} + +void WidgetStateIndicator::initPositionMode() +{ + if (dynamic_cast<QGroupBox*>(widget)) + positionMode = PositionMode::GROUP_BOX; + else if (dynamic_cast<QLabel*>(widget)) + positionMode = PositionMode::LABEL; + else if (dynamic_cast<QCheckBox*>(widget)) + positionMode = PositionMode::CHECK_BOX; +} + +void WidgetStateIndicator::finalInit() +{ + label->setFixedSize(label->pixmap()->size()); + labelParent->setFixedSize(label->pixmap()->size()); + widgetVisible = widget->isVisible(); + labelParent->setVisible(false); +} + +void WidgetStateIndicator::setMessage(const QString& msg) +{ + static const QString paraTpl = QStringLiteral("<p>%1</p>"); + if (msg.startsWith("<p>") && msg.endsWith("</p>")) + message = msg; + else + message = paraTpl.arg(msg); + + label->setToolTip(message); + if (!msg.isNull()) + label->setCursor(Qt::WhatsThisCursor); + else + label->unsetCursor(); +} + +void WidgetStateIndicator::clearMessage() +{ + message = QString::null; + label->setToolTip(message); + label->unsetCursor(); +} + +void WidgetStateIndicator::detectWindowParent() +{ + if (windowParent) + windowParent->removeEventFilter(this); + + windowParent = findParentWindow(widget); + windowParent->installEventFilter(this); + if (labelParent) + labelParent->setParent(windowParent); +} + +void WidgetStateIndicator::setMode(WidgetStateIndicator::Mode mode) +{ + this->mode = mode; + updateMode(); +} + +void WidgetStateIndicator::show(const QString& msg, bool animated) +{ + visibilityRequested = true; + setMessage(msg); + if (animated && animation->state() != QAbstractAnimation::Running) + animation->start(); + + updateVisibility(); +} + +void WidgetStateIndicator::hide() +{ + visibilityRequested = false; + clearMessage(); + if (animation->state() == QAbstractAnimation::Running) + animation->stop(); + + updateVisibility(); +} + +void WidgetStateIndicator::setVisible(bool visible, const QString& msg) +{ + if (visible) + show(msg); + else + hide(); +} + +void WidgetStateIndicator::release() +{ + setVisible(false); + instances.remove(widget); + deleteLater(); +} + +void WidgetStateIndicator::info(const QString& msg, bool animated) +{ + setMode(Mode::INFO); + show(msg, animated); +} + +void WidgetStateIndicator::warn(const QString& msg, bool animated) +{ + setMode(Mode::WARNING); + show(msg, animated); +} + +void WidgetStateIndicator::error(const QString& msg, bool animated) +{ + setMode(Mode::ERROR); + show(msg, animated); +} + +void WidgetStateIndicator::hint(const QString& msg, bool animated) +{ + setMode(Mode::HINT); + show(msg, animated); +} + +bool WidgetStateIndicator::exists(QWidget* widget) +{ + return instances.contains(widget); +} + +WidgetStateIndicator* WidgetStateIndicator::getInstance(QWidget* widget) +{ + if (!instances.contains(widget)) + instances[widget] = new WidgetStateIndicator(widget); + + return instances[widget]; +} + +bool WidgetStateIndicator::eventFilter(QObject* obj, QEvent* ev) +{ + if (obj == widget) + { + switch (ev->type()) + { + case QEvent::Move: + case QEvent::Resize: + case QEvent::Scroll: + updatePosition(); + break; + case QEvent::Show: + widgetVisible = true; + updateVisibility(); + break; + case QEvent::Hide: + widgetVisible = false; + updateVisibility(); + break; + case QEvent::EnabledChange: + updateVisibility(); + break; + default: + break; + } + } + else if (obj == windowParent) + { + switch (ev->type()) + { + case QEvent::ParentChange: + detectWindowParent(); + break; + default: + break; + } + } + else if (obj == label) + { + if (ev->type() == QEvent::Enter) + highlightingEffect->setEnabled(true); + else if (ev->type() == QEvent::Leave) + highlightingEffect->setEnabled(false); + } + + return false; +} + +void WidgetStateIndicator::updateMode() +{ + switch (mode) + { + case Mode::ERROR: + label->setPixmap(ICONS.INDICATOR_ERROR); + glowEffect->setColor(Qt::red); + break; + case Mode::WARNING: + label->setPixmap(ICONS.INDICATOR_WARN); + glowEffect->setColor(Qt::darkYellow); + break; + case Mode::INFO: + label->setPixmap(ICONS.INDICATOR_INFO); + glowEffect->setColor(Qt::blue); + break; + case Mode::HINT: + label->setPixmap(ICONS.INDICATOR_HINT); + glowEffect->setColor(Qt::darkCyan); + break; + } +} + +void WidgetStateIndicator::updatePosition() +{ + switch (positionMode) + { + case PositionMode::DEFAULT: + updatePositionDefault(); + break; + case PositionMode::GROUP_BOX: + updatePositionGroupBox(); + break; + case PositionMode::LABEL: + updatePositionLabel(); + break; + case PositionMode::CHECK_BOX: + updatePositionCheckBox(); + break; + } +} + +void WidgetStateIndicator::updatePositionDefault() +{ + QPoint xy = widget->mapTo(windowParent, QPoint(0,0)); + labelParent->move(xy + QPoint(-4, -4)); +} + +void WidgetStateIndicator::updatePositionGroupBox() +{ + QPoint xy = widget->mapTo(windowParent, QPoint(0,0)); + + QGroupBox* gb = dynamic_cast<QGroupBox*>(widget); + + QFont font = gb->font(); + QFontMetrics fm(font); + QString txt = gb->title(); + QPoint diff(fm.width(txt), 2); + + labelParent->move(xy + diff); +} + +void WidgetStateIndicator::updatePositionLabel() +{ + updatePositionCheckBox(); // currently they're equal +} + +void WidgetStateIndicator::updatePositionCheckBox() +{ + QPoint xy = widget->mapTo(windowParent, QPoint(0,0)); + labelParent->move(xy + QPoint(-6, -2)); +} + +void WidgetStateIndicator::updateVisibility() +{ + if (shouldHide()) + { + labelParent->setVisible(false); + } + else if (shouldShow()) + { + updatePosition(); + labelParent->setVisible(true); + } +} + +bool WidgetStateIndicator::shouldHide() +{ + if (!labelParent->isVisible()) + return false; + + if (!widgetVisible) + return true; + + if (!visibilityRequested) + return true; + + if (!widget->isEnabled()) + return true; + + return false; +} + +bool WidgetStateIndicator::shouldShow() +{ + if (labelParent->isVisible()) + return false; + + if (!widget->isEnabled()) + return false; + + if (!widgetVisible) + return false; + + if (!visibilityRequested) + return false; + + return true; +} +WidgetStateIndicator::PositionMode WidgetStateIndicator::getPositionMode() const +{ + return positionMode; +} + +void WidgetStateIndicator::setPositionMode(const PositionMode& value) +{ + positionMode = value; +} + +QWidget* WidgetStateIndicator::findParentWindow(QWidget* w) +{ + while (w && !w->windowFlags().testFlag(Qt::Window) && !dynamic_cast<QScrollArea*>(w) && !dynamic_cast<MdiChild*>(w)) + w = w->parentWidget(); + + if (dynamic_cast<QScrollArea*>(w)) + return dynamic_cast<QScrollArea*>(w)->widget(); + + return w; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h new file mode 100644 index 0000000..eee49d5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h @@ -0,0 +1,99 @@ +#ifndef WIDGETSTATEINDICATOR_H +#define WIDGETSTATEINDICATOR_H + +#include "guiSQLiteStudio_global.h" +#include <QObject> + +class QLabel; +class QGraphicsDropShadowEffect; +class QGraphicsColorizeEffect; +class QSequentialAnimationGroup; + +class GUI_API_EXPORT WidgetStateIndicator : public QObject +{ + Q_OBJECT + public: + enum class Mode + { + INFO, + ERROR, + WARNING, + HINT + }; + + enum class PositionMode + { + DEFAULT, + GROUP_BOX, + LABEL, + CHECK_BOX, + }; + + ~WidgetStateIndicator(); + + void setMode(Mode mode); + void show(const QString& msg = QString(), bool animated = true); + void hide(); + void setVisible(bool visible, const QString& msg = QString()); + void release(); + void info(const QString& msg, bool animated = true); + void warn(const QString& msg, bool animated = true); + void error(const QString& msg, bool animated = true); + void hint(const QString& msg, bool animated = true); + + static bool exists(QWidget* widget); + static WidgetStateIndicator* getInstance(QWidget* widget); + + PositionMode getPositionMode() const; + void setPositionMode(const PositionMode& value); + + protected: + bool eventFilter(QObject *obj, QEvent *ev); + + private: + explicit WidgetStateIndicator(QWidget *widget); + + void initLabel(); + void initEffects(); + void initGlowEffects(); + void initHighlightingEffects(); + void initAnimations(); + void initPositionMode(); + void finalInit(); + void setMessage(const QString& msg); + void clearMessage(); + void detectWindowParent(); + QWidget* findParentWindow(QWidget* w); + bool shouldHide(); + bool shouldShow(); + + QWidget* labelParent = nullptr; + QLabel* label = nullptr; + Mode mode = Mode::ERROR; + QWidget* widget = nullptr; + QString message; + QGraphicsColorizeEffect* highlightingEffect = nullptr; + QGraphicsDropShadowEffect* glowEffect = nullptr; + QSequentialAnimationGroup* animation = nullptr; + bool widgetVisible = false; + bool visibilityRequested = false; + QWidget* windowParent = nullptr; + PositionMode positionMode = PositionMode::DEFAULT; + + static QHash<QWidget*,WidgetStateIndicator*> instances; + + private slots: + void updateMode(); + void updatePosition(); + void updatePositionDefault(); + void updatePositionGroupBox(); + void updatePositionLabel(); + void updatePositionCheckBox(); + void updateVisibility(); + +}; + +#define INDICATOR(w) WidgetStateIndicator::getInstance(w) +#define EXISTS_INDICATOR(w) WidgetStateIndicator::exists(w) + +#endif // WIDGETSTATEINDICATOR_H |
