diff options
author | Lukáš Lalinský <lalinsky@gmail.com> | 2008-12-07 01:05:09 +0100 |
---|---|---|
committer | Lukáš Lalinský <lalinsky@gmail.com> | 2008-12-07 01:05:09 +0100 |
commit | 364531ec14761f07b22dbec6461376aad81159f6 (patch) | |
tree | d12861cb31805dfb551a9b1361a5c8513bbfd7a3 /src | |
parent | 67ee0e1c5b11cc2a4c70018f524c9ce00da30b60 (diff) | |
download | dbmodel-364531ec14761f07b22dbec6461376aad81159f6.tar.xz |
Move source code to src/ and add translation support
Diffstat (limited to 'src')
32 files changed, 2992 insertions, 0 deletions
diff --git a/src/column.cpp b/src/column.cpp new file mode 100644 index 0000000..e8a355b --- /dev/null +++ b/src/column.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "column.h" +#include "columnlist.h" + +Column::Column(ColumnList *columnList) + : QObject(columnList), m_primaryKey(false), m_required(false) +{ +} + +void +Column::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + emit propertyChanged("name", name); + } +} + +void +Column::setNotes(const QString ¬es) +{ + if (m_notes != notes) { + m_notes = notes; + emit propertyChanged("notes", notes); + } +} + +void +Column::setDataType(const QString &dataType) +{ + if (m_dataType != dataType) { + m_dataType = dataType; + emit propertyChanged("dataType", dataType); + } +} + +void +Column::setPrimaryKey(bool primaryKey) +{ + if (m_primaryKey != primaryKey) { + m_primaryKey = primaryKey; + emit propertyChanged("primaryKey", primaryKey); + } +} + +void +Column::setRequired(bool required) +{ + if (m_required != required) { + m_required = required; + emit propertyChanged("required", required); + } +} diff --git a/src/column.h b/src/column.h new file mode 100644 index 0000000..2930efa --- /dev/null +++ b/src/column.h @@ -0,0 +1,65 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef COLUMN_H +#define COLUMN_H + +#include <QObject> +#include <QString> +#include "columnlist.h" + +class Column : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(QString notes READ notes WRITE setNotes) + Q_PROPERTY(QString dataType READ dataType WRITE setDataType) + Q_PROPERTY(bool primaryKey READ isPrimaryKey WRITE setPrimaryKey) + Q_PROPERTY(bool required READ isRequired WRITE setRequired) + +public: + + Column(ColumnList *columnList = 0); + + ColumnList *columnList() const { return qobject_cast<ColumnList *>(parent()); } + + QString name() const { return m_name; } + void setName(const QString &name); + + QString notes() const { return m_notes; } + void setNotes(const QString ¬es); + + QString dataType() const { return m_dataType; } + void setDataType(const QString &dataType); + + bool isPrimaryKey() const { return m_primaryKey; } + void setPrimaryKey(bool primaryKey); + + bool isRequired() const { return m_required; } + void setRequired(bool required); + +signals: + void propertyChanged(const QString &name, const QVariant &value); + +private: + QString m_name; + QString m_notes; + QString m_dataType; + bool m_primaryKey; + bool m_required; +}; + +#endif diff --git a/src/columnlist.cpp b/src/columnlist.cpp new file mode 100644 index 0000000..b1ed623 --- /dev/null +++ b/src/columnlist.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QDebug> +#include "columnlist.h" +#include "column.h" +#include "databasetable.h" + +ColumnList::ColumnList(DatabaseTable *table) + : QObject(table) +{ +} + +int +ColumnList::insertColumn(int index, Column *column) +{ + Q_ASSERT(m_columns.contains(column) == false); + column->setParent(this); + connect(column, SIGNAL(propertyChanged(const QString &, const QVariant &)), this, SLOT(updateColumn(const QString &, const QVariant &))); + emit columnAboutToBeInserted(index); + m_columns.insert(index, column); + emit columnInserted(index); + return index; +} + +int +ColumnList::appendColumn(Column *column) +{ + return insertColumn(columnCount(), column); +} + +int +ColumnList::removeColumn(Column *column) +{ + int index = m_columns.indexOf(column); + removeColumn(index); + return index; +} + +Column * +ColumnList::removeColumn(int index) +{ + emit columnAboutToBeRemoved(index); + Column *column = m_columns.takeAt(index); + emit columnRemoved(index); + disconnect(column, 0, this, 0); + column->setParent(0); + return column; +} + +void +ColumnList::swapColumns(int oldIndex, int newIndex) +{ + m_columns.move(oldIndex, newIndex); + emit columnChanged(oldIndex); + emit columnChanged(newIndex); +} + +void +ColumnList::updateColumn(const QString &, const QVariant &) +{ + Column *column = qobject_cast<Column *>(sender()); + int index = m_columns.indexOf(column); + Q_ASSERT(index != -1); + emit columnChanged(index); +} diff --git a/src/columnlist.h b/src/columnlist.h new file mode 100644 index 0000000..51a25be --- /dev/null +++ b/src/columnlist.h @@ -0,0 +1,57 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef COLUMNLIST_H +#define COLUMNLIST_H + +#include <QAbstractTableModel> +#include "databasetable.h" +class Column; + +class ColumnList : public QObject +{ + Q_OBJECT + +public: + ColumnList(DatabaseTable *table); + + DatabaseTable *table() { return qobject_cast<DatabaseTable *>(parent()); } + + QList<Column *> columns() { return m_columns; } + int columnCount() const { return m_columns.size(); } + int indexOf(Column *column) const { return m_columns.indexOf(column); } + Column *column(int index) { return m_columns[index]; } + int insertColumn(int, Column *); + int appendColumn(Column *); + void swapColumns(int, int); + int removeColumn(Column *); + Column *removeColumn(int); + +signals: + void columnAboutToBeInserted(int index); + void columnInserted(int index); + void columnAboutToBeRemoved(int index); + void columnRemoved(int index); + void columnChanged(int index); + +protected slots: + void updateColumn(const QString &name, const QVariant&); + +private: + QList<Column *> m_columns; +}; + +#endif diff --git a/src/columnlistmodel.cpp b/src/columnlistmodel.cpp new file mode 100644 index 0000000..5a282c8 --- /dev/null +++ b/src/columnlistmodel.cpp @@ -0,0 +1,208 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QDebug> +#include "columnlistmodel.h" +#include "diagramdocument.h" +#include "databasetable.h" +#include "commands.h" +#include "column.h" +#include "columnlist.h" + +#define COLUMN_COUNT 5 + +ColumnListModel::ColumnListModel(QObject *parent) + : QAbstractTableModel(parent), m_columnList(0) +{ +} + +void +ColumnListModel::setColumnList(ColumnList *columnList) +{ + if (m_columnList) { + disconnect(m_columnList, 0, this, 0); + } + m_columnList = columnList; + if (m_columnList) { + connect(m_columnList, SIGNAL(columnAboutToBeInserted(int)), this, SLOT(_columnAboutToBeInserted(int))); + connect(m_columnList, SIGNAL(columnInserted(int)), this, SLOT(_columnInserted())); + connect(m_columnList, SIGNAL(columnAboutToBeRemoved(int)), this, SLOT(_columnAboutToBeRemoved(int))); + connect(m_columnList, SIGNAL(columnRemoved(int)), this, SLOT(_columnRemoved())); + connect(m_columnList, SIGNAL(columnChanged(int)), this, SLOT(_columnChanged(int))); + } + reset(); +} + +int +ColumnListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_columnList ? m_columnList->columnCount() : 0; +} + +int +ColumnListModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return COLUMN_COUNT; +} + +QVariant +ColumnListModel::data(const QModelIndex &index, int role) const +{ + if (m_columnList) { + Q_ASSERT(index.isValid()); + Column *column = m_columnList->column(index.row()); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + if (index.column() == 0) { + return column->name(); + } + if (index.column() == 1) { + return column->dataType(); + } + if (index.column() == 4) { + return column->notes(); + } + } + if (role == Qt::CheckStateRole) { + if (index.column() == 2) { + return column->isRequired() ? Qt::Checked : Qt::Unchecked; + } + if (index.column() == 3) { + return column->isPrimaryKey() ? Qt::Checked : Qt::Unchecked; + } + } + } + return QVariant(); +} + +bool +ColumnListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (m_columnList) { + Q_ASSERT(index.isValid()); + Column *column = m_columnList->column(index.row()); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + QString text = value.toString(); + if (index.column() == 0) { + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "name", text)); + goto OK; + } + if (index.column() == 1) { + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "dataType", text)); + goto OK; + } + if (index.column() == 4) { + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "notes", text)); + goto OK; + } + } + if (role == Qt::CheckStateRole) { + bool checked = value.toInt() == Qt::Checked ? true : false; + if (index.column() == 2) { + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "required", checked)); + goto OK; + } + if (index.column() == 3) { + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "primaryKey", checked)); + if (!column->isRequired()) { + // TODO is there a simple way to group this with the previous command? + m_columnList->table()->model()->undoStack()->push( + new SetObjectPropertyCommand(column, "required", true)); + } + goto OK; + } + } + } + return false; +OK: + emit dataChanged(createIndex(index.row(), 0), createIndex(index.row(), COLUMN_COUNT)); + return true; +} + +Qt::ItemFlags +ColumnListModel::flags(const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); + if (index.column() == 0 || index.column() == 1 || index.column() == 4) { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; + } + if (index.column() == 2 || index.column() == 3) { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + } + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +QVariant +ColumnListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + if (section == 0) + return tr("Name"); + if (section == 1) + return tr("Data Type"); + if (section == 2) + return tr("Req'd"); + if (section == 3) + return tr("PK"); + if (section == 4) + return tr("Notes"); + } + return QVariant(); +} + +QModelIndex +ColumnListModel::indexFromRow(int i) const +{ + return createIndex(i, 0); +} + +void +ColumnListModel::_columnAboutToBeInserted(int index) +{ + beginInsertRows(QModelIndex(), index, index); +} + +void +ColumnListModel::_columnInserted() +{ + endInsertRows(); +} + +void +ColumnListModel::_columnAboutToBeRemoved(int index) +{ + beginRemoveRows(QModelIndex(), index, index); +} + +void +ColumnListModel::_columnRemoved() +{ + endRemoveRows(); +} + +void +ColumnListModel::_columnChanged(int index) +{ + emit dataChanged(createIndex(index, 0), createIndex(index, COLUMN_COUNT)); +} diff --git a/src/columnlistmodel.h b/src/columnlistmodel.h new file mode 100644 index 0000000..2944223 --- /dev/null +++ b/src/columnlistmodel.h @@ -0,0 +1,55 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef COLUMNLISTMODEL_H +#define COLUMNLISTMODEL_H + +#include <QAbstractTableModel> +class Column; +class ColumnList; + +class ColumnListModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ColumnListModel(QObject *parent = 0); + + ColumnList *columnList() { return m_columnList; } + void setColumnList(ColumnList *columnList); + + // QAbstractItemModel methods + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QModelIndex indexFromRow(int i) const; + +private slots: + void _columnAboutToBeInserted(int index); + void _columnInserted(); + void _columnAboutToBeRemoved(int index); + void _columnRemoved(); + void _columnChanged(int index); + +private: + ColumnList *m_columnList; +}; + +#endif diff --git a/src/columnlistview.cpp b/src/columnlistview.cpp new file mode 100644 index 0000000..dd73fb6 --- /dev/null +++ b/src/columnlistview.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "columnlist.h" +#include "columnlistmodel.h" +#include "columnlistview.h" +#include "commands.h" +#include "diagramdocument.h" + +ColumnListView::ColumnListView(QWidget *parent) + : QTreeView(parent) +{ + setModel(new ColumnListModel(this)); +} + +void +ColumnListView::setColumnList(ColumnList *columnList) +{ + columnListModel()->setColumnList(columnList); +} + +void +ColumnListView::addColumn() +{ + AddColumnCommand *command = new AddColumnCommand(columnList()); + columnList()->table()->model()->undoStack()->push(command); + QModelIndex index = columnListModel()->indexFromRow(command->index()); + setCurrentIndex(index); + edit(index); +} + +void +ColumnListView::removeColumn() +{ + QList<int> indexes = selectedColumns(); + if (indexes.size() == 1) { + columnList()->table()->model()->undoStack()->push( + new RemoveColumnCommand(columnList(), indexes[0])); + } +} + +void +ColumnListView::moveColumnDown() +{ + QList<int> indexes = selectedColumns(); + if (indexes.size() == 1) { + int i = indexes[0]; + if (i + 1 < columnList()->columnCount()) { + columnList()->table()->model()->undoStack()->push( + new SwapColumnsCommand(columnList(), i, i + 1)); + setCurrentIndex(columnListModel()->indexFromRow(i + 1)); + } + } +} + +void +ColumnListView::moveColumnUp() +{ + QList<int> indexes = selectedColumns(); + if (indexes.size() == 1) { + int i = indexes[0]; + if (i > 0) { + columnList()->table()->model()->undoStack()->push( + new SwapColumnsCommand(columnList(), i, i - 1)); + setCurrentIndex(columnListModel()->indexFromRow(i - 1)); + } + } +} + +QModelIndexList +ColumnListView::selectedIndexes() const +{ + QModelIndexList indexes; + foreach (QModelIndex index, selectionModel()->selectedIndexes()) { + if (index.column() == 0) { + indexes << index; + } + } + return indexes; +} + +QList<int> +ColumnListView::selectedColumns() const +{ + QList<int> columns; + foreach (QModelIndex index, selectedIndexes()) { + columns << index.row(); + } + return columns; +} diff --git a/src/columnlistview.h b/src/columnlistview.h new file mode 100644 index 0000000..8e2bb47 --- /dev/null +++ b/src/columnlistview.h @@ -0,0 +1,47 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef COLUMNLISTVIEW_H +#define COLUMNLISTVIEW_H + +#include <QTreeView> +#include "columnlistmodel.h" + +class ColumnListView : public QTreeView +{ + Q_OBJECT + +public: + ColumnListView(QWidget *parent = 0); + + void setColumnList(ColumnList *columnList); + ColumnListModel *columnListModel() { return qobject_cast<ColumnListModel *>(model()); } + + ColumnList *columnList() { return columnListModel()->columnList(); } + + QList<int> selectedColumns() const; + +public slots: + void addColumn(); + void removeColumn(); + void moveColumnUp(); + void moveColumnDown(); + +protected: + QModelIndexList selectedIndexes() const; +}; + +#endif diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..ca23345 --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,127 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QDebug> +#include "commands.h" +#include "databasetable.h" +#include "column.h" + +SetObjectPropertyCommand::SetObjectPropertyCommand(QObject *object, const char *name, const QVariant &value, QUndoCommand *parent) + : QUndoCommand(parent), m_object(object), m_name(name), m_newValue(value) +{ + m_oldValue = object->property(name); +} + +void +SetObjectPropertyCommand::redo() +{ + m_object->setProperty(m_name.latin1(), m_newValue); +} + +void +SetObjectPropertyCommand::undo() +{ + m_object->setProperty(m_name.latin1(), m_oldValue); +} + +int +SetObjectPropertyCommand::id() const +{ + return COMMAND_SET_OBJECT_PROPERTY; +} + +bool +SetObjectPropertyCommand::mergeWith(const QUndoCommand *o) +{ + return false; + Q_ASSERT(id() == o->id()); + const SetObjectPropertyCommand *other = static_cast<const SetObjectPropertyCommand *>(o); + if (m_object != other->m_object || m_name != other->m_name) + return false; + m_newValue = other->m_newValue; + return true; +} + + +AddColumnCommand::AddColumnCommand(ColumnList *columnList, QUndoCommand *parent) + : QUndoCommand(parent), m_columnList(columnList) +{ + m_column = new Column(); + m_index = columnList->columnCount(); +} + +AddColumnCommand::~AddColumnCommand() +{ + if (m_column) + delete m_column; +} + +void +AddColumnCommand::redo() +{ + m_columnList->insertColumn(m_index, m_column); + m_column = 0; +} + +void +AddColumnCommand::undo() +{ + m_column = m_columnList->removeColumn(m_index); +} + + +RemoveColumnCommand::RemoveColumnCommand(ColumnList *columnList, int index, QUndoCommand *parent) + : QUndoCommand(parent), m_columnList(columnList), m_index(index), m_column(0) +{ +} + +RemoveColumnCommand::~RemoveColumnCommand() +{ + if (m_column) + delete m_column; +} + +void +RemoveColumnCommand::redo() +{ + m_column = m_columnList->removeColumn(m_index); +} + +void +RemoveColumnCommand::undo() +{ + Q_ASSERT(m_column != 0); + m_columnList->insertColumn(m_index, m_column); + m_column = 0; +} + + +SwapColumnsCommand::SwapColumnsCommand(ColumnList *columnList, int oldIndex, int newIndex, QUndoCommand *parent) + : QUndoCommand(parent), m_columnList(columnList), m_oldIndex(oldIndex), m_newIndex(newIndex) +{ +} + +void +SwapColumnsCommand::redo() +{ + m_columnList->swapColumns(m_oldIndex, m_newIndex); +} + +void +SwapColumnsCommand::undo() +{ + m_columnList->swapColumns(m_newIndex, m_oldIndex); +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..53fc947 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,88 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef COMMANDS_H +#define COMMANDS_H + +#include <QUndoCommand> +#include <QVariant> +#include <QLatin1String> + +class DatabaseTable; +class Column; +#include "column.h" + +enum { + COMMAND_SET_OBJECT_PROPERTY, +}; + +class SetObjectPropertyCommand : public QUndoCommand +{ +public: + SetObjectPropertyCommand(QObject *object, const char *name, const QVariant &value, QUndoCommand *parent = 0); + void undo(); + void redo(); + int id() const; + bool mergeWith(const QUndoCommand *command); + +private: + QObject *m_object; + QLatin1String m_name; + QVariant m_oldValue, m_newValue; +}; + +class AddColumnCommand : public QUndoCommand +{ +public: + AddColumnCommand(ColumnList *columnList, QUndoCommand *parent = 0); + ~AddColumnCommand(); + void undo(); + void redo(); + int index() { return m_index; } + +private: + ColumnList *m_columnList; + int m_index; + Column *m_column; +}; + +class RemoveColumnCommand : public QUndoCommand +{ +public: + RemoveColumnCommand(ColumnList *columnList, int index, QUndoCommand *parent = 0); + ~RemoveColumnCommand(); + void undo(); + void redo(); + +private: + ColumnList *m_columnList; + int m_index; + Column *m_column; +}; + +class SwapColumnsCommand : public QUndoCommand +{ +public: + SwapColumnsCommand(ColumnList *columnList, int oldIndex, int newIndex, QUndoCommand *parent = 0); + void undo(); + void redo(); + +private: + ColumnList *m_columnList; + int m_oldIndex, m_newIndex; +}; + +#endif diff --git a/src/databaserelationship.cpp b/src/databaserelationship.cpp new file mode 100644 index 0000000..d7c3dd1 --- /dev/null +++ b/src/databaserelationship.cpp @@ -0,0 +1,243 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QGraphicsScene> +#include <QDebug> +#include "databasetable.h" +#include "databaserelationship.h" +#include "range.h" + +DatabaseRelationship::DatabaseRelationship(DiagramItem *parent) + : DiagramConnection(parent) +{ + setFlag(ItemIsSelectable); + connect(this, SIGNAL(endPointChanged()), this, SLOT(updateLayout())); +} + +QRectF +DatabaseRelationship::boundingRect() const +{ + return shape().boundingRect(); + //return QRectF(m_line.p1(), m_line.p2()).normalized(); +} + +QPainterPath +DatabaseRelationship::shape() const +{ + QPainterPath path; + if (m_line.isEmpty()) + return path; + path.moveTo(m_line[0]); + for (int i = 1; i < m_line.size(); i++) + path.lineTo(m_line[i]); + path.addPolygon(m_arrowHead); + QPen pen(QPen(QColor(0, 0, 0), 1)); + pen.setJoinStyle(Qt::MiterJoin); + QPainterPathStroker ps; + ps.setCapStyle(pen.capStyle()); + ps.setWidth(pen.widthF() + 0.3); + ps.setJoinStyle(pen.joinStyle()); + ps.setMiterLimit(pen.miterLimit()); + return ps.createStroke(path); +} + +void +DatabaseRelationship::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + QPen pen(QPen(QColor(0, 0, 0), 1)); + pen.setJoinStyle(Qt::MiterJoin); + painter->setPen(pen); + painter->drawPolyline(m_line); + painter->setBrush(pen.color()); + painter->drawPolygon(m_arrowHead); +} + +QVariant +DatabaseRelationship::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemSceneHasChanged) { + updateLayout(); + } + return QGraphicsItem::itemChange(change, value); +} + +void +DatabaseRelationship::updatePositions() +{ + updateLayout(); +} + +void +DatabaseRelationship::updateLayout() +{ + if (!scene()) + return; + + DatabaseTable *source = sourceTable(); + DatabaseTable *target = targetTable(); + if (!source || !target) + return; + + prepareGeometryChange(); + + QRectF rect1 = source->boundingRect().translated(source->pos()); + QRectF rect2 = target->boundingRect().translated(target->pos()); + + bool haveLine = false; + + Range<qreal> verticalRange1(rect1.top(), rect1.bottom()); + Range<qreal> verticalRange2(rect2.top(), rect2.bottom()); + Range<qreal> verticalIntersection = verticalRange1.intersected(verticalRange2); + + Range<qreal> horizontalRange1(rect1.left(), rect1.right()); + Range<qreal> horizontalRange2(rect2.left(), rect2.right()); + Range<qreal> horizontalIntersection = horizontalRange1.intersected(horizontalRange2); + + // Straight horizontal line + if (!haveLine) { + if (!verticalIntersection.isNull() && horizontalIntersection.isNull()) { + qreal y = verticalIntersection.min() + verticalIntersection.length() / 2; + if (rect1.right() < rect2.left()) { + m_line = QPolygonF() << QPointF(rect1.right(), y) << QPointF(rect2.left(), y); + } + else { + m_line = QPolygonF() << QPointF(rect1.left(), y) << QPointF(rect2.right(), y); + } + haveLine = true; + } + } + + // Straight vertical line + if (!haveLine) { + if (verticalIntersection.isNull() && !horizontalIntersection.isNull()) { + qreal x = horizontalIntersection.min() + horizontalIntersection.length() / 2; + if (rect1.bottom() < rect2.top()) { + m_line = QPolygonF() << QPointF(x, rect1.bottom()) << QPointF(x, rect2.top()); + } + else { + m_line = QPolygonF() << QPointF(x, rect1.top()) << QPointF(x, rect2.bottom()); + } + haveLine = true; + } + } + + // Two-segment line + if (!haveLine) { + + qreal x1, x2, x3, y1, y2, y3; + + if (rect1.right() < rect2.left()) { + x1 = rect1.right(); + y1 = verticalRange1.center(); + + x2 = horizontalRange2.center(); + y2 = verticalRange1.center(); + + if (rect1.bottom() < rect2.top()) { + x3 = horizontalRange2.center(); + y3 = rect2.top(); + } + else { + x3 = horizontalRange2.center(); + y3 = rect2.bottom(); + } + } + else { + x1 = rect1.left(); + y1 = verticalRange1.center(); + + x2 = horizontalRange2.center(); + y2 = verticalRange1.center(); + + if (rect1.bottom() < rect2.top()) { + x3 = horizontalRange2.center(); + y3 = rect2.top(); + } + else { + x3 = horizontalRange2.center(); + y3 = rect2.bottom(); + } + } + + m_line = QPolygonF() + << QPointF(x1, y1) + << QPointF(x2, y2) + << QPointF(x3, y3); + haveLine = true; + + +/* + if (verticalIntersection.isNull() && !horizontalIntersection.isNull()) { + qreal x = horizontalIntersection.min() + horizontalIntersection.length() / 2; + if (rect1.bottom() < rect2.top()) { + m_line = QLineF(x, rect1.bottom(), x, rect2.top()); + } + else { + m_line = QLineF(x, rect1.top(), x, rect2.bottom()); + } + haveLine = true; + }*/ + } + + // Simple center<->center line + if (!haveLine) { + QPointF p1 = rect1.center(); + QPointF p2 = rect2.center(); + QLineF finalLine = QLineF(p1, p2); + + QPolygonF polygon; + + polygon = rect1; + for (int i = 0; i < 4; i++) { + QLineF line(polygon[i], polygon[(i+1)%4]); + QPointF intersectionPoint; + if (finalLine.intersect(line, &intersectionPoint) == QLineF::BoundedIntersection) { + finalLine.setP1(intersectionPoint); + break; + } + } + + polygon = rect2; + for (int i = 0; i < 4; i++) { + QLineF line(polygon[i], polygon[(i+1)%4]); + QPointF intersectionPoint; + if (finalLine.intersect(line, &intersectionPoint) == QLineF::BoundedIntersection) { + finalLine.setP2(intersectionPoint); + break; + } + } + + m_line = QPolygonF() << finalLine.p1() << finalLine.p2(); + haveLine = true; + } + + Q_ASSERT(haveLine); + + int size = m_line.size(); + QLineF lastSegment(m_line[size-2], m_line[size-1]); + + m_arrowHead.resize(3); + m_arrowHead[0] = QPointF(0.0, 0.0); + m_arrowHead[1] = QPointF(-4.5, 10.0); + m_arrowHead[2] = QPointF(4.5, 10.0); + QMatrix matrix; + matrix.rotate(-lastSegment.angle() + 90); + m_arrowHead = matrix.map(m_arrowHead); + m_arrowHead.translate(lastSegment.p2()); +} diff --git a/src/databaserelationship.h b/src/databaserelationship.h new file mode 100644 index 0000000..f0edd68 --- /dev/null +++ b/src/databaserelationship.h @@ -0,0 +1,55 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DATABASERELATION_H +#define DATABASERELATION_H + +#include <QGraphicsItem> +#include <QPainter> +#include "diagramconnection.h" +#include "databasetable.h" + +class DatabaseRelationship : public DiagramConnection +{ + Q_OBJECT + +public: + DatabaseRelationship(DiagramItem *parent = 0); + + QRectF boundingRect() const; + QPainterPath shape() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + DatabaseTable *sourceTable() { return qobject_cast<DatabaseTable *>(source()); } + DatabaseTable *targetTable() { return qobject_cast<DatabaseTable *>(target()); } + + enum { Type = DiagramItem::Relation }; + virtual int type() const { return Type; } + + void updatePositions(); + +protected slots: + void updateLayout(); + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private: + QPolygonF m_line; + QPolygonF m_arrowHead; +}; + +#endif diff --git a/src/databasetable.cpp b/src/databasetable.cpp new file mode 100644 index 0000000..968a3f2 --- /dev/null +++ b/src/databasetable.cpp @@ -0,0 +1,178 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QGraphicsScene> +#include <QDebug> +#include "databasetable.h" +#include "diagramdocument.h" +#include "column.h" +#include "columnlist.h" + +DatabaseTable::DatabaseTable(DiagramItem *parent) + : DiagramObject(parent) +{ + setFlag(ItemIsMovable); + setFlag(ItemIsSelectable); + m_name = "Table"; + m_columnList = new ColumnList(this); + connect(m_columnList, SIGNAL(columnInserted(int)), this, SLOT(updateLayout())); + connect(m_columnList, SIGNAL(columnRemoved(int)), this, SLOT(updateLayout())); + connect(m_columnList, SIGNAL(columnChanged(int)), this, SLOT(updateLayout())); +} + +DiagramDocument * +DatabaseTable::model() const +{ + return qobject_cast<DiagramDocument *>(scene()); +} + +QRectF +DatabaseTable::boundingRect() const +{ + return m_outerRect; +} + +void +DatabaseTable::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + QFont font = scene()->font(); + QFont boldFont = font; + boldFont.setBold(true); + QFont boldFontWithUnderline = boldFont; + boldFontWithUnderline.setUnderline(true); + + QPen pen(QPen(QColor(0, 0, 0))); + pen.setJoinStyle(Qt::MiterJoin); + QPen borderPen(pen); + if (isSelected()) { + borderPen.setColor(QColor(0, 96, 255)); + borderPen.setWidth(2); + } + + painter->setPen(pen); + painter->setFont(font); + + painter->fillRect(m_outerRect, QColor(255, 255, 255)); + + // Draw the table name + painter->fillRect(m_nameBgRect, QColor(205, 205, 205)); + painter->drawLine(m_nameBgRect.bottomLeft(), m_nameBgRect.bottomRight()); + painter->drawText(m_namePos, m_name); + + painter->drawLine( + m_leftSideWidth, m_nameBgRect.bottom(), + m_leftSideWidth, m_outerRect.bottom()); + + // Draw the table name + QPointF colPos = m_firstColPos; + QPointF leftSizePos = colPos - QPointF(m_leftSideWidth, 0); + foreach (Column *column, m_columnList->columns()) { + painter->setFont(column->isRequired() + ? (column->isPrimaryKey() ? boldFontWithUnderline : boldFont) + : font); + painter->drawText(colPos, column->name()); + if (column->isPrimaryKey()) { + painter->setFont(column->isRequired() ? boldFont : font); + painter->drawText(leftSizePos, "PK"); + } + colPos += m_colPosIncrement; + leftSizePos += m_colPosIncrement; + } + + // Draw the outside border + painter->setPen(borderPen); + painter->drawRect(m_outerRect); +} + +void +DatabaseTable::setName(const QString &name) +{ + m_name = name; + updateLayout(); + emit propertyChanged("name", name); +} + +QVariant +DatabaseTable::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemSceneHasChanged) { + updateLayout(); + } + if (change == ItemPositionChange) { + DiagramDocument *model = qobject_cast<DiagramDocument *>(scene()); + if (model) { + emit model->tableMoved(this); + } + } + return QGraphicsItem::itemChange(change, value); +} + +void +DatabaseTable::updateLayout() +{ + if (!scene()) + return; + + prepareGeometryChange(); + + QFont font = scene()->font(); + QFontMetricsF fontMetrics(font); + + QFont boldFont = font; + boldFont.setBold(true); + QFontMetricsF boldFontMetrics(boldFont); + + qreal spaceWidth = fontMetrics.width(' '); + qreal nameWidth = fontMetrics.width(m_name); + qreal height = fontMetrics.height(); + + m_leftSideWidth = fontMetrics.width("PK"); + + qreal width = nameWidth + spaceWidth * 2; + + qreal maxColumnWidth = 0; + foreach (Column *column, m_columnList->columns()) { + qreal columnWidth = column->isRequired() ? boldFontMetrics.width(column->name()) : fontMetrics.width(column->name()); + maxColumnWidth = qMax(maxColumnWidth, columnWidth); + qreal columnLeftSideWidth = 0; + if (column->isPrimaryKey()) { + if (column->isRequired()) { + columnLeftSideWidth = boldFontMetrics.width("PK"); + } + else { + columnLeftSideWidth = fontMetrics.width("PK"); + } + } + m_leftSideWidth = qMax(m_leftSideWidth, columnLeftSideWidth); + } + m_leftSideWidth += spaceWidth * 2; + width = qMax(nameWidth + spaceWidth * 4, m_leftSideWidth + maxColumnWidth + spaceWidth * 2); + + qreal nameHeight = height + spaceWidth; + m_outerRect = QRectF(0, 0, width, nameHeight + qMax(0.66, qreal(m_columnList->columnCount())) * fontMetrics.lineSpacing() + - fontMetrics.leading() + fontMetrics.descent()); + + m_nameBgRect = QRectF(0, 0, width, nameHeight); + m_namePos = QPointF((width - nameWidth) / 2, fontMetrics.ascent() + spaceWidth / 2); + + m_firstColPos = QPointF(m_leftSideWidth + spaceWidth * 1 - 1, nameHeight + fontMetrics.ascent() + 2); + m_colPosIncrement = QPointF(0, fontMetrics.lineSpacing()); + + update(); +} diff --git a/src/databasetable.h b/src/databasetable.h new file mode 100644 index 0000000..5765a97 --- /dev/null +++ b/src/databasetable.h @@ -0,0 +1,72 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DATABASETABLE_H +#define DATABASETABLE_H + +#include <QGraphicsItem> +#include <QPainter> +#include "diagramobject.h" + +class Column; +class ColumnList; +class DiagramDocument; + +class DatabaseTable : public DiagramObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName) + +public: + DatabaseTable(DiagramItem *parent = 0); + + DiagramDocument *model() const; + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + QString name() const { return m_name; } + void setName(const QString &name); + + ColumnList *columnList() { return m_columnList; } + +// void setColumn(int index, const QString &name); +// void removeColumn(int index); +// Column *addColumn(const QString &name = QString()); +// void swapColumns(int index1, int index2); + + enum { Type = DiagramItem::Table }; + virtual int type() const { return Type; } + +signals: + void propertyChanged(const QString &name, const QVariant &value); + +protected slots: + void updateLayout(); + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private: + QString m_name; + ColumnList *m_columnList; + + QRectF m_outerRect, m_nameBgRect; + QPointF m_namePos, m_firstColPos, m_colPosIncrement; + qreal m_leftSideWidth; +}; + +#endif diff --git a/src/diagramconnection.cpp b/src/diagramconnection.cpp new file mode 100644 index 0000000..a996994 --- /dev/null +++ b/src/diagramconnection.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "diagramconnection.h" + +DiagramConnection::DiagramConnection(DiagramItem *parent) + : DiagramItem(parent) +{ + m_objects[0] = 0; + m_objects[1] = 0; +} + +void +DiagramConnection::setSource(DiagramObject *object) +{ + m_objects[0] = object; + emit endPointChanged(); +} + +void +DiagramConnection::setTarget(DiagramObject *object) +{ + m_objects[1] = object; + emit endPointChanged(); +} diff --git a/src/diagramconnection.h b/src/diagramconnection.h new file mode 100644 index 0000000..26810a7 --- /dev/null +++ b/src/diagramconnection.h @@ -0,0 +1,43 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DIAGRAMCONNECTION_H +#define DIAGRAMCONNECTION_H + +#include "diagramitem.h" +class DiagramObject; + +class DiagramConnection : public DiagramItem +{ + Q_OBJECT + +public: + DiagramConnection(DiagramItem *parent = 0); + + DiagramObject *source() { return m_objects[0]; } + DiagramObject *target() { return m_objects[1]; } + + void setSource(DiagramObject *object); + void setTarget(DiagramObject *object); + +signals: + void endPointChanged(); + +private: + DiagramObject *m_objects[2]; +}; + +#endif diff --git a/src/diagramdocument.cpp b/src/diagramdocument.cpp new file mode 100644 index 0000000..573d08c --- /dev/null +++ b/src/diagramdocument.cpp @@ -0,0 +1,298 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "diagramdocument.h" +#include "databasetable.h" +#include "databaserelationship.h" +#include "column.h" +#include <QGraphicsItem> +#include <QDebug> +#include <QFile> +#include <QTextStream> + +DiagramDocument::DiagramDocument(QObject *parent) + : QGraphicsScene(parent), m_mode(DiagramDocument::Select), m_line(NULL) +{ + m_undoStack = new QUndoStack(this); + connect(this, SIGNAL(tableMoved(DatabaseTable *)), SLOT(updatePositions(DatabaseTable *))); +} + +DiagramDocument::Mode +DiagramDocument::mode() +{ + return m_mode; +} + +void +DiagramDocument::setMode(Mode mode) +{ + m_mode = mode; + emit modeChanged(mode); +} + +void +DiagramDocument::updatePositions(DatabaseTable *table) +{ + foreach (DatabaseRelationship *relation, findTableRelations(table)) { + relation->updatePositions(); + } +} + +void +DiagramDocument::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_mode == AddTable && event->button() == Qt::LeftButton) { + DatabaseTable *table = new DatabaseTable(); + table->setPos(event->scenePos()); + table->setZValue(10.0); + m_tables << table; + addItem(table); + clearSelection(); + table->setSelected(true); + setMode(Select); + event->accept(); + return; + } + if (m_mode == AddRelation && event->button() == Qt::LeftButton) { + m_line = new QGraphicsLineItem(); + m_line->setLine(QLineF(event->scenePos(), event->scenePos())); + m_line->setZValue(1000.0); + addItem(m_line); + event->accept(); + return; + } + QGraphicsScene::mousePressEvent(event); +} + +void +DiagramDocument::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_line) { + m_line->setLine(QLineF(m_line->line().p1(), event->scenePos())); + event->accept(); + return; + } + QGraphicsScene::mouseMoveEvent(event); +} + +void +DiagramDocument::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_line) { + removeItem(m_line); + DatabaseTable *source = qgraphicsitem_cast<DatabaseTable *>(itemAt(m_line->line().p1())); + DatabaseTable *target = qgraphicsitem_cast<DatabaseTable *>(itemAt(m_line->line().p2())); + if (source && target && source != target) { + //qDebug() << "Add relation between " << source << " and " << target; + DatabaseRelationship *relation = new DatabaseRelationship(); + relation->setSource(source); + relation->setTarget(target); + relation->setZValue(1.0); + addItem(relation); + m_relations << relation; + } + + delete m_line; + m_line = NULL; + setMode(Select); + event->accept(); + return; + } + QGraphicsScene::mouseReleaseEvent(event); +} + +DatabaseTable * +DiagramDocument::selectedTable() +{ + QList<QGraphicsItem *> items = selectedItems(); + if (items.size() != 1) + return NULL; + return qgraphicsitem_cast<DatabaseTable *>(items[0]); +} + +void +DiagramDocument::deleteSelectedItems() +{ + foreach (QGraphicsItem *item, selectedItems()) { + DatabaseTable *table = qgraphicsitem_cast<DatabaseTable *>(item); + if (table) { + foreach (DatabaseRelationship *relation, findTableRelations(table)) { + removeItem(relation); + m_relations.removeAll(relation); + } + removeItem(table); + m_tables.removeAll(table); + } + } +} + +QList<DatabaseRelationship *> +DiagramDocument::findTableRelations(DatabaseTable *table) +{ + QList<DatabaseRelationship *> result; + foreach (DatabaseRelationship *relation, m_relations) { + if (relation->source() == table || relation->target() == table) { + result << relation; + } + } + return result; +} + +void +appendStringElement(QDomDocument &doc, QDomElement &parent, const QString &name, const QString &value) +{ + if (!value.isEmpty()) { + QDomElement element = doc.createElement(name); + element.appendChild(doc.createTextNode(value)); + parent.appendChild(element); + } +} + +QString +readStringElement(QDomElement &parent, const QString &name, const QString &defaultValue = QString()) +{ + QDomElement element = parent.firstChildElement(name); + if (!element.isNull()) { + return element.text(); + } + return defaultValue; +} + +void +DiagramDocument::save(const QString &fileName) +{ + QDomDocument doc; + + QDomElement root = doc.createElement("database-model"); + doc.appendChild(root); + + QDomElement tableListElement = doc.createElement("table-list"); + root.appendChild(tableListElement); + + foreach (DatabaseTable *table, m_tables) { + QDomElement tableElement = doc.createElement("table"); + tableListElement.appendChild(tableElement); + + QDomElement positionElement = doc.createElement("position"); + positionElement.setAttribute("x", table->pos().x()); + positionElement.setAttribute("y", table->pos().y()); + tableElement.appendChild(positionElement); + + QDomElement nameElement = doc.createElement("name"); + nameElement.appendChild(doc.createTextNode(table->name())); + tableElement.appendChild(nameElement); + + QDomElement columnListElement = doc.createElement("column-list"); + tableElement.appendChild(columnListElement); + + foreach (Column *column, table->columnList()->columns()) { + QDomElement columnElement = doc.createElement("column"); + columnListElement.appendChild(columnElement); + appendStringElement(doc, columnElement, "name", column->name()); + appendStringElement(doc, columnElement, "data-type", column->dataType()); + appendStringElement(doc, columnElement, "required", column->isRequired() ? "yes" : QString()); + appendStringElement(doc, columnElement, "primary-key", column->isPrimaryKey() ? "yes" : QString()); + appendStringElement(doc, columnElement, "notes", column->notes()); + } + } + + QDomElement relationListElement = doc.createElement("relation-list"); + root.appendChild(relationListElement); + + foreach (DatabaseRelationship *relation, m_relations) { + QDomElement relationElement = doc.createElement("relation"); + relationElement.setAttribute("from", QString::number(m_tables.indexOf(relation->sourceTable()))); + relationElement.setAttribute("to", QString::number(m_tables.indexOf(relation->targetTable()))); + relationListElement.appendChild(relationElement); + } + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly)) { + QTextStream stream(&file); + doc.save(stream, 0); + file.close(); + } + + setFileName(fileName); +} + +void +DiagramDocument::load(const QString &fileName) +{ + QDomDocument doc; + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + doc.setContent(&file); + file.close(); + } + + setFileName(fileName); + + QDomElement root = doc.firstChildElement("database-model"); + + QDomElement tableListElement = root.firstChildElement("table-list"); + QDomElement tableElement = tableListElement.firstChildElement("table"); + while (!tableElement.isNull()) { + DatabaseTable *table = new DatabaseTable; + + QDomElement positionElement = tableElement.firstChildElement("position"); + if (!positionElement.isNull()) { + QPoint pos; + pos.setX(positionElement.attribute("x", "0").toInt()); + pos.setY(positionElement.attribute("y", "0").toInt()); + table->setPos(pos); + } + + table->setName(readStringElement(tableElement, "name")); + + QDomElement columnListElement = tableElement.firstChildElement("column-list"); + QDomElement columnElement = columnListElement.firstChildElement("column"); + while (!columnElement.isNull()) { + Column *column = new Column(); + column->setName(readStringElement(columnElement, "name")); + column->setDataType(readStringElement(columnElement, "data-type")); + column->setRequired(readStringElement(columnElement, "required") == "yes"); + column->setPrimaryKey(readStringElement(columnElement, "primary-key") == "yes"); + column->setNotes(readStringElement(columnElement, "notes")); + table->columnList()->appendColumn(column); + columnElement = columnElement.nextSiblingElement("column"); + } + + m_tables << table; + addItem(table); + + tableElement = tableElement.nextSiblingElement("table"); + } + + QDomElement relationListElement = root.firstChildElement("relation-list"); + QDomElement relationElement = relationListElement.firstChildElement("relation"); + while (!relationElement.isNull()) { + relationElement = relationElement.nextSiblingElement("relation"); + bool ok; + int index0 = relationElement.attribute("from").toInt(&ok); + if (ok) { + int index1 = relationElement.attribute("to").toInt(&ok); + if (ok) { + DatabaseRelationship *relation = new DatabaseRelationship(); + relation->setSource(m_tables[index0]); + relation->setTarget(m_tables[index1]); + m_relations << relation; + addItem(relation); + } + } + } + +} diff --git a/src/diagramdocument.h b/src/diagramdocument.h new file mode 100644 index 0000000..b7890f6 --- /dev/null +++ b/src/diagramdocument.h @@ -0,0 +1,80 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DATABASEMODEL_H +#define DATABASEMODEL_H + +#include <QUndoStack> +#include <QGraphicsScene> +#include <QGraphicsSceneMouseEvent> +#include <QDomDocument> +#include <QGraphicsLineItem> + +class DatabaseTable; +class DatabaseRelationship; + +class DiagramDocument : public QGraphicsScene +{ + Q_OBJECT + +public: + DiagramDocument(QObject *parent = 0); + + enum Mode { + Select = 0, + AddTable, + AddRelation + }; + + Mode mode(); + void setMode(Mode mode); + + DatabaseTable *selectedTable(); + void deleteSelectedItems(); + QList<DatabaseRelationship *> findTableRelations(DatabaseTable *table); + + void save(const QString &fileName); + void load(const QString &fileName); + + QString fileName() { return m_fileName; } + void setFileName(const QString &fileName) { m_fileName = fileName; } + + QUndoStack *undoStack() const { return m_undoStack; } + +signals: + void modeChanged(DiagramDocument::Mode mode); + void tableMoved(DatabaseTable *table); + + friend class DatabaseTable; + +protected slots: + void updatePositions(DatabaseTable *table); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + +private: + QUndoStack *m_undoStack; + Mode m_mode; + QString m_fileName; + QGraphicsLineItem *m_line; + QList<DatabaseTable *> m_tables; + QList<DatabaseRelationship *> m_relations; +}; + +#endif diff --git a/src/diagramitem.cpp b/src/diagramitem.cpp new file mode 100644 index 0000000..ddb6269 --- /dev/null +++ b/src/diagramitem.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "diagramitem.h" + +DiagramItem::DiagramItem(DiagramItem *parent) + : QGraphicsItem(parent) +{ +} diff --git a/src/diagramitem.h b/src/diagramitem.h new file mode 100644 index 0000000..f66a48c --- /dev/null +++ b/src/diagramitem.h @@ -0,0 +1,33 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DATABASEMODELITEM_H +#define DATABASEMODELITEM_H + +#include <QGraphicsItem> + +class DiagramItem : public QObject, public QGraphicsItem +{ +public: + enum { + Table = UserType + 1, + Relation + }; + + DiagramItem(DiagramItem *parent = 0); +}; + +#endif diff --git a/src/diagramobject.cpp b/src/diagramobject.cpp new file mode 100644 index 0000000..8b0d5b3 --- /dev/null +++ b/src/diagramobject.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "diagramobject.h" + +DiagramObject::DiagramObject(DiagramItem *parent) + : DiagramItem(parent) +{ +} diff --git a/src/diagramobject.h b/src/diagramobject.h new file mode 100644 index 0000000..1ca086e --- /dev/null +++ b/src/diagramobject.h @@ -0,0 +1,28 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DIAGRAMOBJECT_H +#define DIAGRAMOBJECT_H + +#include "diagramitem.h" + +class DiagramObject : public DiagramItem +{ +public: + DiagramObject(DiagramItem *parent = 0); +}; + +#endif diff --git a/src/diagramview.cpp b/src/diagramview.cpp new file mode 100644 index 0000000..490cca6 --- /dev/null +++ b/src/diagramview.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QMouseEvent> +#include <QScrollBar> +#include <QDebug> +#include "diagramview.h" + +DiagramView::DiagramView(QWidget *parent) + : QGraphicsView(parent), m_handScrolling(false) +{ + setAlignment(Qt::AlignLeft | Qt::AlignTop); + setRenderHint(QPainter::Antialiasing); +} + +void +DiagramView::setScene(QGraphicsScene *scene) +{ + QGraphicsView::setScene(scene); + updateSceneRect2(scene->sceneRect()); + connect(scene, SIGNAL(sceneRectChanged(const QRectF &)), SLOT(updateSceneRect2(const QRectF &))); +} + +void +DiagramView::updateSceneRect2(const QRectF &rect) +{ + QRectF sceneRect = rect.united(QRectF(QPointF(0, 0), QPointF(100, 100))); + setSceneRect(sceneRect); +} + +void +DiagramView::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::MidButton) { + m_handScrolling = true; + m_handScrollingOrigin = QPoint(event->pos()); + m_handScrollingValueX = horizontalScrollBar()->value(); + m_handScrollingValueY = verticalScrollBar()->value(); + m_savedCursor = viewport()->cursor(); + viewport()->setCursor(Qt::ClosedHandCursor); + event->accept(); + return; + } + QGraphicsView::mousePressEvent(event); +} + +void +DiagramView::mouseMoveEvent(QMouseEvent *event) +{ + if (m_handScrolling) { + QPoint delta = event->pos() - m_handScrollingOrigin; + horizontalScrollBar()->setValue(m_handScrollingValueX - delta.x()); + verticalScrollBar()->setValue(m_handScrollingValueY - delta.y()); + event->accept(); + return; + } + QGraphicsView::mouseMoveEvent(event); +} + +void +DiagramView::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_handScrolling) { + m_handScrolling = false; + viewport()->setCursor(m_savedCursor); + event->accept(); + return; + } + QGraphicsView::mouseReleaseEvent(event); +} diff --git a/src/diagramview.h b/src/diagramview.h new file mode 100644 index 0000000..d5f1c56 --- /dev/null +++ b/src/diagramview.h @@ -0,0 +1,47 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef DATABASEMODELVIEW_H +#define DATABASEMODELVIEW_H + +#include <QGraphicsView> + +class DiagramView : public QGraphicsView +{ + Q_OBJECT + +public: + DiagramView(QWidget *parent = 0); + + void setScene(QGraphicsScene *scene); + +protected: + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + +protected slots: + void updateSceneRect2(const QRectF &rect); + +private: + bool m_handScrolling; + QPoint m_handScrollingOrigin; + QCursor m_savedCursor; + int m_handScrollingValueX; + int m_handScrollingValueY; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..79a7a4b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QDebug> +#include <QTranslator> +#include <QApplication> +#include "mainwindow.h" + +int +main(int argc, char **argv) +{ + Q_INIT_RESOURCE(dbmodel); + QApplication app(argc, argv); + + QTranslator translator; + translator.load("dbmodel_" + QLocale::system().name(), ":/translations/"); + app.installTranslator(&translator); + + MainWindow window; + window.show(); + return app.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..1afb262 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,330 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <cmath> +#include <QMenuBar> +#include <QMenu> +#include <QComboBox> +#include <QToolBar> +#include <QFileDialog> +#include <QIcon> +#include <QImage> +#include <QSvgGenerator> +#include <QMessageBox> +#include <QDebug> +#include "mainwindow.h" +#include "databasetable.h" + +using namespace std; + +MainWindow::MainWindow() + : QMainWindow(), m_model(NULL) +{ + m_undoGroup = new QUndoGroup(this); + setupActions(); + setupUi(); + newModel(); +} + +MainWindow::~MainWindow() +{ + if (m_model) { + m_undoGroup->removeStack(m_model->undoStack()); + } +} + +void +MainWindow::setupUi() +{ + setWindowTitle(tr("DB Model")); + resize(QSize(780, 580)); + + setupToolBar(); + setupMenuBar(); + + m_properties = new TableProperties(this, this); + + m_view = new DiagramView(this); + + m_splitter = new QSplitter(Qt::Vertical, this); + setCentralWidget(m_splitter); + m_splitter->addWidget(m_view); + m_splitter->addWidget(m_properties); + + m_splitter->setStretchFactor(0, 6); + m_splitter->setStretchFactor(1, 1); +} + +void +MainWindow::setupActions() +{ + m_actionNew = new QAction(this); + m_actionNew->setText(tr("&New")); + m_actionNew->setIcon(QIcon(":/icons/16x16/document-new.png")); + m_actionNew->setShortcut(QKeySequence(tr("Ctrl+N"))); + connect(m_actionNew, SIGNAL(triggered(bool)), SLOT(newModel())); + + m_actionOpen = new QAction(this); + m_actionOpen->setText(tr("&Open...")); + m_actionOpen->setIcon(QIcon(":/icons/16x16/document-open.png")); + m_actionOpen->setShortcut(QKeySequence(tr("Ctrl+O"))); + connect(m_actionOpen, SIGNAL(triggered(bool)), SLOT(open())); + + m_actionSave = new QAction(this); + m_actionSave->setText(tr("&Save")); + m_actionSave->setIcon(QIcon(":/icons/16x16/document-save.png")); + m_actionSave->setShortcut(QKeySequence(tr("Ctrl+S"))); + m_actionSave->setDisabled(true); + connect(m_actionSave, SIGNAL(triggered(bool)), SLOT(save())); + + m_actionSaveAs = new QAction(this); + m_actionSaveAs->setText(tr("Save &As...")); + m_actionSaveAs->setIcon(QIcon(":/icons/16x16/document-save-as.png")); + m_actionSaveAs->setDisabled(true); + connect(m_actionSaveAs, SIGNAL(triggered(bool)), SLOT(saveAs())); + + connect(m_undoGroup, SIGNAL(cleanChanged(bool)), m_actionSave, SLOT(setDisabled(bool))); + connect(m_undoGroup, SIGNAL(cleanChanged(bool)), m_actionSaveAs, SLOT(setDisabled(bool))); + + m_actionExportPNG = new QAction(this); + m_actionExportPNG->setText(tr("Export...")); + connect(m_actionExportPNG, SIGNAL(triggered(bool)), SLOT(exportPNG())); + + m_actionSwitchMode[0] = new QAction(this); + m_actionSwitchMode[0]->setText(tr("Select")); + m_actionSwitchMode[0]->setIcon(QIcon(":/icons/cr16-action-mouse_pointer.png")); + m_actionSwitchMode[0]->setCheckable(true); + m_actionSwitchMode[0]->setChecked(true); + + m_actionSwitchMode[1] = new QAction(this); + m_actionSwitchMode[1]->setText(tr("Add new table")); + m_actionSwitchMode[1]->setIcon(QIcon(":/icons/cr16-action-table_newobj.png")); + m_actionSwitchMode[1]->setCheckable(true); + + m_actionSwitchMode[2] = new QAction(this); + m_actionSwitchMode[2]->setText(tr("Add new relation")); + m_actionSwitchMode[2]->setIcon(QIcon(":/icons/cr16-action-relation_newobj.png")); + m_actionSwitchMode[2]->setCheckable(true); + + m_actionUndo = m_undoGroup->createUndoAction(this, tr("&Undo")); + m_actionUndo->setShortcut(QKeySequence(tr("Ctrl+Z"))); + m_actionUndo->setIcon(QIcon(":/icons/16x16/edit-undo.png")); + m_actionRedo = m_undoGroup->createRedoAction(this, tr("Re&do")); + m_actionRedo->setShortcut(QKeySequence(tr("Ctrl+Shift+Z"))); + m_actionRedo->setIcon(QIcon(":/icons/16x16/edit-redo.png")); + + connect(m_actionSwitchMode[0], SIGNAL(triggered(bool)), SLOT(switchModeSelect())); + connect(m_actionSwitchMode[1], SIGNAL(triggered(bool)), SLOT(switchModeAddTable())); + connect(m_actionSwitchMode[2], SIGNAL(triggered(bool)), SLOT(switchModeAddRelation())); +} + +void +MainWindow::setupToolBar() +{ + QToolBar *toolBar = addToolBar(tr("&File")); + toolBar->setIconSize(QSize(16, 16)); + toolBar->addAction(m_actionNew); + toolBar->addAction(m_actionOpen); + toolBar->addSeparator(); + toolBar->addAction(m_actionSave); + toolBar->addAction(m_actionSaveAs); + toolBar->addSeparator(); + toolBar->addAction(m_actionUndo); + toolBar->addAction(m_actionRedo); + toolBar = addToolBar(tr("&Mode")); + toolBar->setIconSize(QSize(16, 16)); + toolBar->addAction(m_actionSwitchMode[0]); + toolBar->addAction(m_actionSwitchMode[1]); + toolBar->addAction(m_actionSwitchMode[2]); + + QComboBox *sceneScaleCombo = new QComboBox; + QStringList scales; + scales << tr("50%") << tr("70%") << tr("85%") << tr("100%") << tr("125%") << tr("150%"); + sceneScaleCombo->addItems(scales); + sceneScaleCombo->setEditable(true); + sceneScaleCombo->setCurrentIndex(3); + connect(sceneScaleCombo, SIGNAL(currentIndexChanged(const QString &)), + this, SLOT(setViewScale(const QString &))); + + toolBar = addToolBar(tr("View")); + toolBar->addWidget(sceneScaleCombo); + +} + +void +MainWindow::setViewScale(const QString &scale) +{ + double newScale = scale.left(scale.indexOf(tr("%"))).toDouble() / 100.0; + QMatrix oldMatrix = m_view->matrix(); + m_view->resetMatrix(); + m_view->translate(oldMatrix.dx(), oldMatrix.dy()); + m_view->scale(newScale, newScale); +} + +void +MainWindow::setupMenuBar() +{ + QMenu *menu = menuBar()->addMenu(tr("&File")); + menu->addAction(m_actionNew); + menu->addAction(m_actionOpen); + menu->addSeparator(); + menu->addAction(m_actionSave); + menu->addAction(m_actionSaveAs); + menu->addSeparator(); + menu->addAction(m_actionExportPNG); + menu->addSeparator(); + menu->addAction(tr("&Quit"), this, SLOT(close()), QKeySequence(tr("Ctrl+Q"))); + + menu = menuBar()->addMenu(tr("&Edit")); + menu->addAction(m_actionUndo); + menu->addAction(m_actionRedo); + menu->addSeparator(); + menu->addAction(tr("&Delete"), this, SLOT(deleteSelectedItems()), QKeySequence(tr("Del"))); +} + +void +MainWindow::deleteSelectedItems() +{ + m_model->deleteSelectedItems(); +} + +void +MainWindow::open() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), "Database Model (*.dmf)"); + if (!fileName.isNull()) { + DiagramDocument *model = new DiagramDocument(this); + model->load(fileName); + newModel(model); + } +} + +void +MainWindow::save() +{ + QString fileName = m_model->fileName(); + if (fileName.isEmpty()) { + saveAs(); + } + else { + m_model->save(fileName); + } +} + +void +MainWindow::saveAs() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString(), m_model->fileName(), "Database Model (*.dmf)"); + if (!fileName.isNull()) { + m_model->save(fileName); + } +} + +void +MainWindow::exportPNG() +{ + QStringList filters; + filters << "Portable Network Graphics (*.png)"; + filters << "Scalable Vector Graphics (*.svg)"; + QString fileName = QFileDialog::getSaveFileName(this, QString(), QString(), filters.join(";;")); + if (!fileName.isNull()) { + QRectF boundingRect = m_model->itemsBoundingRect().adjusted(-2, -2, 2, 2); + QSize size(int(ceil(boundingRect.width())), int(ceil(boundingRect.height()))); + if (fileName.endsWith(".svg", Qt::CaseInsensitive)) { + QSvgGenerator generator; + generator.setSize(size); + generator.setFileName(fileName); + QPainter painter(&generator); + painter.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::Antialiasing); + m_model->render(&painter, QRectF(), boundingRect); + } + else if (fileName.endsWith(".png", Qt::CaseInsensitive)) { + QImage image(size, QImage::Format_RGB32); + QPainter painter(&image); + painter.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::Antialiasing); + painter.fillRect(image.rect(), Qt::white); + m_model->render(&painter, image.rect(), boundingRect); + if (true) + image.convertToFormat(QImage::Format_Indexed8).save(fileName, "PNG"); + else + image.save(fileName, "PNG"); + } + else { + QMessageBox::critical(this, tr("Error"), tr("Unknown format.")); + } + } +} + +void +MainWindow::newModel(DiagramDocument *newModel) +{ + if (!newModel) + newModel = new DiagramDocument(this); + + m_view->setScene(newModel); + if (m_model) { + m_undoGroup->removeStack(m_model->undoStack()); + delete m_model; + } + m_model = newModel; + updateMode(m_model->mode()); + m_undoGroup->addStack(m_model->undoStack()); + m_undoGroup->setActiveStack(m_model->undoStack()); + + connect(m_model, + SIGNAL(modeChanged(DiagramDocument::Mode)), + SLOT(updateMode(DiagramDocument::Mode))); + connect(m_model, SIGNAL(selectionChanged()), SLOT(updateSelection())); +} + +void +MainWindow::updateSelection() +{ + DatabaseTable *table = m_model->selectedTable(); + m_properties->setTable(table); +} + +void +MainWindow::updateMode(DiagramDocument::Mode mode) +{ + m_actionSwitchMode[0]->setChecked(mode == DiagramDocument::Select); + m_actionSwitchMode[1]->setChecked(mode == DiagramDocument::AddTable); + m_actionSwitchMode[2]->setChecked(mode == DiagramDocument::AddRelation); +} + +void +MainWindow::switchModeSelect() +{ + m_model->setMode(DiagramDocument::Select); +} + +void +MainWindow::switchModeAddTable() +{ + m_model->setMode(DiagramDocument::AddTable); +} + +void +MainWindow::switchModeAddRelation() +{ + m_model->setMode(DiagramDocument::AddRelation); +} + +QUndoStack * +MainWindow::currentUndoStack() +{ + return m_undoGroup->activeStack(); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..bd8e6f8 --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,78 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QSplitter> +#include <QStackedWidget> +#include <QUndoGroup> +#include <QAction> +#include "diagramview.h" +#include "diagramdocument.h" +#include "tableproperties.h" + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + ~MainWindow(); + + QUndoStack *currentUndoStack(); + +public slots: + void newModel(DiagramDocument *newModel = 0); + + void updateMode(DiagramDocument::Mode mode); + void switchModeSelect(); + void switchModeAddTable(); + void switchModeAddRelation(); + + void updateSelection(); + void deleteSelectedItems(); + + void open(); + void save(); + void saveAs(); + void exportPNG(); + void setViewScale(const QString &scale); + +protected: + void setupUi(); + void setupActions(); + void setupToolBar(); + void setupMenuBar(); + +private: + QUndoGroup *m_undoGroup; + QSplitter *m_splitter; + TableProperties *m_properties; + DiagramView *m_view; + DiagramDocument *m_model; + + QAction *m_actionUndo, *m_actionRedo; + QAction *m_actionSwitchMode[3]; + QAction *m_actionNew; + QAction *m_actionOpen; + QAction *m_actionSave; + QAction *m_actionSaveAs; + QAction *m_actionExportPNG; +}; + +#endif diff --git a/src/range.h b/src/range.h new file mode 100644 index 0000000..1c0bbf1 --- /dev/null +++ b/src/range.h @@ -0,0 +1,69 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include <QDebug> + +template <class T> +class Range { +public: + Range() : m_min(0), m_max(0) {} + Range(T min, T max) + { + Q_ASSERT(min <= max); + m_min = min; + m_max = max; + } + + T min() const { return m_min; } + T max() const { return m_max; } + + bool isNull() const { return m_max == m_min; } + + T length() const { return m_max - m_min; } + T center() const { return m_min + length() / 2; } + + Range<T> intersected(const Range<T> &other) const + { + const Range<T> *a = this; + const Range<T> *b = &other; + + if (a->isNull()) return *a; + if (b->isNull()) return *b; + + if (a->min() > b->min()) + qSwap(a, b); + + if (a->max() < b->min()) + return Range<T>(); + + T p1 = b->min(); + T p2 = qMin(a->max(), b->max()); + + return Range<T>(p1, p2); + } + +private: + T m_min; + T m_max; +}; + + +template <class T> inline +QDebug operator<<(QDebug dbg, const Range<T> &r) +{ + dbg.nospace() << "Range(" << r.min() << ", " << r.max() << ")"; + return dbg.space(); +} diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..6a6fa5a --- /dev/null +++ b/src/src.pro @@ -0,0 +1,60 @@ +TARGET = dbmodel + +QT += xml svg +CONFIG += debug +SOURCES = \ + column.cpp \ + columnlist.cpp \ + columnlistmodel.cpp \ + columnlistview.cpp \ + main.cpp \ + mainwindow.cpp \ + diagramview.cpp \ + diagramdocument.cpp \ + databasetable.cpp \ + databaserelationship.cpp \ + diagramconnection.cpp \ + diagramitem.cpp \ + diagramobject.cpp \ + tableproperties.cpp \ + commands.cpp +HEADERS = \ + column.h \ + columnlist.h \ + columnlistmodel.h \ + columnlistview.h \ + mainwindow.h \ + diagramview.h \ + diagramdocument.h \ + databasetable.h \ + databaserelationship.h \ + diagramconnection.h \ + diagramitem.h \ + diagramobject.h \ + tableproperties.h \ + range.h \ + commands.h +FORMS = tableproperties.ui +RESOURCES = ../dbmodel.qrc + +DESTDIR = ../ + +TRANSLATIONS = \ + ../translations/dbmodel_sk.ts + +isEmpty(QMAKE_LRELEASE) { + win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\lrelease.exe + else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease +} + +updateqm.input = TRANSLATIONS +updateqm.output = ../translations/${QMAKE_FILE_BASE}.qm +updateqm.commands = $$QMAKE_LRELEASE ${QMAKE_FILE_IN} -qm ../translations/${QMAKE_FILE_BASE}.qm +updateqm.CONFIG += no_link +QMAKE_EXTRA_COMPILERS += updateqm +PRE_TARGETDEPS += compiler_updateqm_make_all + +MOC_DIR = $$PWD/.build +OBJECTS_DIR = $$PWD/.build +UI_DIR = $$PWD/.build +RCC_DIR = $$PWD/.build diff --git a/src/tableproperties.cpp b/src/tableproperties.cpp new file mode 100644 index 0000000..b54e4db --- /dev/null +++ b/src/tableproperties.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "tableproperties.h" +#include "databasetable.h" +#include "mainwindow.h" +#include "commands.h" +#include "column.h" +#include <QCheckBox> +#include <QDebug> + +TableProperties::TableProperties(MainWindow *window, QWidget *parent) + : QWidget(parent), m_window(window), m_table(0) +{ + ui.setupUi(this); + connect(ui.nameEdit, + SIGNAL(textEdited(const QString &)), + SLOT(setSelectedTableName(const QString &))); + connect(ui.addColumnButton, SIGNAL(clicked()), ui.columnsWidget, SLOT(addColumn())); + connect(ui.removeColumnButton, SIGNAL(clicked()), ui.columnsWidget, SLOT(removeColumn())); + connect(ui.moveColumnUpButton, SIGNAL(clicked()), ui.columnsWidget, SLOT(moveColumnUp())); + connect(ui.moveColumnDownButton, SIGNAL(clicked()), ui.columnsWidget, SLOT(moveColumnDown())); + connect(ui.columnsWidget->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + SLOT(updateColumnSelection())); +} + +void +TableProperties::setSelectedTableName(const QString &name) +{ + if (m_table) { + m_window->currentUndoStack()->push(new SetObjectPropertyCommand(m_table, "name", name)); + } +} + +void +TableProperties::setTable(DatabaseTable *table) +{ + // Disconnect all connections from the previous table + if (m_table) { + disconnect(m_table, 0, this, 0); + } + + m_table = NULL; + if (table == NULL) { + setEnabled(false); + ui.nameEdit->clear(); + ui.columnsWidget->setColumnList(0); + } + else { + setEnabled(true); + ui.nameEdit->setText(table->name()); + ui.columnsWidget->setColumnList(table->columnList()); + connect(table, SIGNAL(propertyChanged(const QString &, const QVariant &)), SLOT(updateProperty(const QString &, const QVariant &))); + } + m_table = table; + updateColumnSelection(); +} + +void +TableProperties::updateProperty(const QString &name, const QVariant &value) +{ + if (name == "name") { + ui.nameEdit->setText(value.toString()); + } +} + +void +TableProperties::updateColumnSelection() +{ + QList<int> columns = ui.columnsWidget->selectedColumns(); + if (columns.isEmpty()) { + ui.removeColumnButton->setEnabled(false); + ui.moveColumnUpButton->setEnabled(false); + ui.moveColumnDownButton->setEnabled(false); + } + else { + int index = columns[0]; + ui.removeColumnButton->setEnabled(true); + ui.moveColumnUpButton->setEnabled(index > 0); + ui.moveColumnDownButton->setEnabled(index + 1 < m_table->columnList()->columnCount()); + } +} diff --git a/src/tableproperties.h b/src/tableproperties.h new file mode 100644 index 0000000..6ad51a5 --- /dev/null +++ b/src/tableproperties.h @@ -0,0 +1,48 @@ +// Copyright (C) 2008 Lukas Lalinsky +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef TABLEPROPERTIES_H +#define TABLEPROPERTIES_H + +#include <QWidget> +#include <QTreeWidget> +#include "ui_tableproperties.h" + +class DatabaseTable; +class MainWindow; + +class TableProperties : public QWidget +{ + Q_OBJECT + +public: + TableProperties(MainWindow *window, QWidget *parent = 0); + + DatabaseTable *table() { return m_table; } + void setTable(DatabaseTable *table); + +protected slots: + void setSelectedTableName(const QString &name); + void updateColumnSelection(); + void updateProperty(const QString &name, const QVariant &value); + +private: + MainWindow *m_window; + DatabaseTable *m_table; + Ui_TableProperties ui; +}; + +#endif diff --git a/src/tableproperties.ui b/src/tableproperties.ui new file mode 100644 index 0000000..7336cee --- /dev/null +++ b/src/tableproperties.ui @@ -0,0 +1,137 @@ +<ui version="4.0" > + <class>TableProperties</class> + <widget class="QWidget" name="TableProperties" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>471</width> + <height>192</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout" > + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QTabWidget" name="tabWidget" > + <property name="currentIndex" > + <number>0</number> + </property> + <widget class="QWidget" name="tab" > + <attribute name="title" > + <string>&Definition</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2" > + <item row="0" column="0" > + <widget class="QLabel" name="label" > + <property name="text" > + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QLineEdit" name="nameEdit" /> + </item> + <item row="1" column="0" colspan="2" > + <spacer name="verticalSpacer" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0" > + <size> + <width>158</width> + <height>113</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2" > + <attribute name="title" > + <string>&Columns</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3" > + <item rowspan="5" row="0" column="0" > + <widget class="ColumnListView" name="columnsWidget" > + <property name="rootIsDecorated" > + <bool>false</bool> + </property> + <property name="itemsExpandable" > + <bool>false</bool> + </property> + <property name="expandsOnDoubleClick" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QPushButton" name="addColumnButton" > + <property name="toolTip" > + <string>Add new column</string> + </property> + <property name="text" > + <string>&Add</string> + </property> + </widget> + </item> + <item row="1" column="1" > + <widget class="QPushButton" name="removeColumnButton" > + <property name="toolTip" > + <string>Remove selected column</string> + </property> + <property name="text" > + <string>&Remove</string> + </property> + </widget> + </item> + <item row="2" column="1" > + <widget class="QPushButton" name="moveColumnUpButton" > + <property name="toolTip" > + <string>Move selected column up</string> + </property> + <property name="text" > + <string>Move &Up</string> + </property> + </widget> + </item> + <item row="3" column="1" > + <widget class="QPushButton" name="moveColumnDownButton" > + <property name="toolTip" > + <string>Move selected column down</string> + </property> + <property name="text" > + <string>Move &Down</string> + </property> + </widget> + </item> + <item row="4" column="1" > + <spacer name="verticalSpacer_2" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0" > + <size> + <width>17</width> + <height>21</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ColumnListView</class> + <extends>QTreeView</extends> + <header>columnlistview.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> |