diff --git a/sources/editor/textfieldeditor.cpp b/sources/editor/textfieldeditor.cpp index 725b06f38..f3f2444db 100644 --- a/sources/editor/textfieldeditor.cpp +++ b/sources/editor/textfieldeditor.cpp @@ -17,7 +17,8 @@ */ #include "textfieldeditor.h" #include "parttextfield.h" - +#include "qtextorientationwidget.h" +#include "qetapp.h" /** Constructeur @param editor L'editeur d'element concerne @@ -36,8 +37,20 @@ TextFieldEditor::TextFieldEditor(QETElementEditor *editor, PartTextField *textfi rotate -> setChecked(true); rotation_angle_ = new QDoubleSpinBox(); rotation_angle_ -> setRange(-360.0, 360.0); - rotation_angle_ -> setSingleStep(-90.0); rotation_angle_ -> setSuffix("\260"); + rotation_widget_ = new QTextOrientationWidget(); + rotation_widget_ -> setFont(QETApp::diagramTextsFont()); + rotation_widget_ -> setUsableTexts(QList() + << tr("Q", "Single-letter example text - translate length, not meaning") + << tr("QET", "Small example text - translate length, not meaning") + << tr("Schema", "Normal example text - translate length, not meaning") + << tr("Electrique", "Normal example text - translate length, not meaning") + << tr("QElectroTech", "Long example text - translate length, not meaning") + ); + rotation_widget_ -> setMinimumSize(90.0, 90.0); + connect(rotation_angle_, SIGNAL(valueChanged(double)), rotation_widget_, SLOT(setOrientation(double))); + connect(rotation_widget_, SIGNAL(orientationChanged(double)), rotation_angle_, SLOT(setValue(double))); + connect(rotation_widget_, SIGNAL(orientationChanged(double)), rotation_angle_, SIGNAL(editingFinished())); qle_x -> setValidator(new QDoubleValidator(qle_x)); qle_y -> setValidator(new QDoubleValidator(qle_y)); @@ -64,6 +77,7 @@ TextFieldEditor::TextFieldEditor(QETElementEditor *editor, PartTextField *textfi QHBoxLayout *rotation_angle_layout = new QHBoxLayout(); rotation_angle_layout -> addWidget(new QLabel(tr("Angle de rotation par d\351faut : "))); + rotation_angle_layout -> addWidget(rotation_widget_); rotation_angle_layout -> addWidget(rotation_angle_); main_layout -> addLayout(rotation_angle_layout); @@ -113,7 +127,8 @@ void TextFieldEditor::updateForm() { qle_text -> setText(part -> property("text").toString()); font_size -> setValue(part -> property("size").toInt()); rotate -> setChecked(!part -> property("rotate").toBool()); - rotation_angle_ -> setValue(part -> property("rotation angle").toInt()); + rotation_angle_ -> setValue(part -> property("rotation angle").toDouble()); + rotation_widget_ -> setOrientation(part -> property("rotation angle").toDouble()); activeConnections(true); } diff --git a/sources/editor/textfieldeditor.h b/sources/editor/textfieldeditor.h index 3dec092b0..fbeb6cb6c 100644 --- a/sources/editor/textfieldeditor.h +++ b/sources/editor/textfieldeditor.h @@ -20,6 +20,7 @@ #include #include "elementitemeditor.h" class PartTextField; +class QTextOrientationWidget; /** Cette classe represente un editeur de champ de texte Elle permet d'editer a travers une interface graphique les @@ -42,6 +43,7 @@ class TextFieldEditor : public ElementItemEditor { QSpinBox *font_size; QCheckBox *rotate; QDoubleSpinBox *rotation_angle_; + QTextOrientationWidget *rotation_widget_; // methodes public slots: diff --git a/sources/qtextorientationwidget.cpp b/sources/qtextorientationwidget.cpp new file mode 100644 index 000000000..ac2339d46 --- /dev/null +++ b/sources/qtextorientationwidget.cpp @@ -0,0 +1,349 @@ +/* + Copyright 2006-2010 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech 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. + + QElectroTech 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 QElectroTech. If not, see . +*/ +#include "qtextorientationwidget.h" + +/** + Constructeur + Par defaut, ce widget met en valeur les angles multiples de 45 degres + et presente un texte oriente a 0 degre, avec la police par defaut de + l'application. Le texte affiche est + @param parent Widget parent +*/ +QTextOrientationWidget::QTextOrientationWidget(QWidget *parent) : + QWidget(parent), + squares_interval_(45.0), + current_orientation_(0.0), + display_text_(true), + must_highlight_angle_(false), + read_only_(false) +{ + // chaines par defaut + text_size_hash_.insert(tr("Ex.", "Short example string"), -1); + text_size_hash_.insert(tr("Exemple", "Longer example string"), -1); + + // definit la politique de gestion de la taille de ce widget : + // on prefere la sizeHint() + QSizePolicy size_policy(QSizePolicy::Minimum, QSizePolicy::Minimum); + // on souhaite conserver le rapport entre sa hauteur et sa largeur + size_policy.setHeightForWidth(true); + setSizePolicy(size_policy); + + // suivi de la souris : permet de recevoir les evenements relatifs aux + // mouvement de la souris sans que l'utilisateur n'ait a cliquer + setMouseTracking(true); +} + +/** + Destructeur +*/ +QTextOrientationWidget::~QTextOrientationWidget() { +} + +/** + @param angle la nouvelle orientation / le nouvel angle selectionne(e) + 0 degre correspond a un texte horizontal, de gauche a droite + 90 degres correspondent a un texte vertical de haut en bas +*/ +void QTextOrientationWidget::setOrientation(const double &angle) { + current_orientation_ = angle; + update(); +} + +/** + @return l'orientation / l'angle actuellement selectionne(e) + 0 degre correspond a un texte horizontal, de gauche a droite + 90 degres correspondent a un texte vertical de haut en bas +*/ +double QTextOrientationWidget::orientation() const { + return(current_orientation_); +} + +/** + Definit la police de caracteres a utiliser pour le texte affiche + @param font Une police de caracteres +*/ +void QTextOrientationWidget::setFont(const QFont &font) { + text_font_ = font; + + // invalide le cache contenant les longueurs des textes a disposition + foreach(QString text, text_size_hash_.keys()) { + text_size_hash_[text] = -1; + } +} + +/** + @return la police utilisee pour le texte affiche +*/ +QFont QTextOrientationWidget::font() const { + return(text_font_); +} + +/** + @param display_text true pour afficher un texte, false sinon +*/ +void QTextOrientationWidget::setDisplayText(bool display_text) { + display_text_ = display_text; +} + +/** + @return la police utilisee pour le texte affiche +*/ +bool QTextOrientationWidget::textDisplayed() const { + return(display_text_); +} + +/** + @param texts_list Une liste de chaines de caracteres utilisables par le + widget afin d'afficher un texte en guise d'exemple. Le widget choisit la + chaine la plus appropriee en fonction de sa taille. + Note : la liste fournie ne doit pas etre vide. Utilisez setDisplayText si + vous ne voulez plus afficher de texte. +*/ +void QTextOrientationWidget::setUsableTexts(const QStringList &texts_list) { + if (texts_list.isEmpty()) return; + + // on oublie les anciennes chaines + foreach(QString text, text_size_hash_.keys()) { + // il faut oublier les anciennes chaines + if (!texts_list.contains(text)) { + text_size_hash_.remove(text); + } + } + + // on ajoute les nouvelles, sans les calculer (on met -1 en guise de longueur) + foreach(QString text, texts_list) { + if (!text_size_hash_.contains(text)) { + text_size_hash_[text] = -1; + } + } +} + +/** + @return la liste des chaines dont le widget dispose pour afficher un texte +*/ +QStringList QTextOrientationWidget::usableTexts() const { + return(text_size_hash_.keys()); +} + +/** + @return true si le widget est en mode "lecture seule", false sinon +*/ +bool QTextOrientationWidget::isReadOnly() const { + return(read_only_); +} + +/** + @param ro true pour passer le widget en mode "lecture seule", false sinon +*/ +void QTextOrientationWidget::setReadOnly(bool ro) { + read_only_ = ro; +} + +/** + @return la taille recommandee pour ce widget +*/ +QSize QTextOrientationWidget::sizeHint() const { + return(QSize(50, 50)); +} + +/** + @param w une largeur donnee + @return la hauteur preferee pour une largeur donnee + Pour ce widget : retourne la largeur fournie afin de maintenir le widget carre +*/ +int QTextOrientationWidget::heightForWidth(int w) const { + return(w); +} + +/** + Effectue le rendu du widget + @param event Evenement decrivant la demande de rendu du widget +*/ +void QTextOrientationWidget::paintEvent(QPaintEvent *event) { + Q_UNUSED(event); + + // rectangle de travail avec son centre et son rayon + QRect drawing_rectangle(QPoint(0, 0), size()); + drawing_rectangle.adjust(5, 5, -5, -5); + + QPointF drawing_rectangle_center(drawing_rectangle.center()); + qreal drawing_rectangle_radius = drawing_rectangle.width() / 2.0; + + QPainter p; + p.begin(this); + + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::TextAntialiasing, true); + + // cercle gris a fond jaune + p.setPen(QPen(QBrush(QColor("#9FA8A8")), 2.0)); + p.setBrush(QBrush(QColor("#ffffaa"))); + p.drawEllipse(drawing_rectangle); + + // ligne rouge indiquant l'angle actuel + p.setPen(QPen(QBrush(Qt::red), 1.0)); + p.translate(drawing_rectangle_center); + p.rotate(current_orientation_); + p.drawLine(QLineF(QPointF(), QPointF(drawing_rectangle_radius, 0.0))); + + // texte optionnel + if (display_text_) { + // determine le texte a afficher + QString chosen_text = getMostUsableStringForRadius(drawing_rectangle_radius); + if (!chosen_text.isEmpty()) { + p.resetTransform(); + p.setPen(Qt::black); + p.setFont(text_font_); + p.translate(drawing_rectangle_center); + p.rotate(current_orientation_); + p.drawText(QPoint(), chosen_text); + } + } + + // carres verts a fond vert + qreal squares_size = size().width() / 15.0; + qreal square_offset = - squares_size / 2.0; + QRectF square_qrect = QRect(square_offset, square_offset, squares_size, squares_size); + p.setPen(Qt::NoPen); + p.setBrush(QBrush(QColor("#248A34"))); + for (double drawing_angle = 0.0 ; drawing_angle < 360.0 ; drawing_angle += squares_interval_) { + if (must_highlight_angle_ && highlight_angle_ == drawing_angle && underMouse()) { + p.setBrush(QBrush(QColor("#43FF5F"))); + } + p.resetTransform(); + p.translate(drawing_rectangle_center); + p.rotate(drawing_angle); + p.translate(drawing_rectangle_radius - 1.0, 0.0); + p.rotate(-45.0); + p.drawRect(square_qrect); + if (must_highlight_angle_ && highlight_angle_ == drawing_angle) { + p.setBrush(QBrush(QColor("#248A34"))); + } + } + + p.end(); +} + +/** + Gere les mouvements de la souris sur ce widget + @param event Evenement decrivant le mouvement de la souris +*/ +void QTextOrientationWidget::mouseMoveEvent(QMouseEvent *event) { + if (read_only_) return; + + bool drawn_angle_hovered = positionIsASquare(event -> posF(), &highlight_angle_); + + if (must_highlight_angle_ != drawn_angle_hovered) { + must_highlight_angle_ = drawn_angle_hovered; + update(); + } +} + +/** + Gere les relachements de la souris sur ce widget + @param event Evenement decrivant le relachement de la souris +*/ +void QTextOrientationWidget::mouseReleaseEvent(QMouseEvent *event) { + if (read_only_) return; + + double clicked_angle; + bool drawn_angle_clicked = positionIsASquare(event -> posF(), &clicked_angle); + + if (drawn_angle_clicked) { + setOrientation(clicked_angle); + emit(orientationChanged(clicked_angle)); + must_highlight_angle_ = false; + update(); + } +} + +/** + @param radius Rayon du cercle qui limitera le rendu du texte + @return la chaine la plus appropriee en fonction de la taille du widget. +*/ +QString QTextOrientationWidget::getMostUsableStringForRadius(const qreal &radius) { + // s'assure que l'on connait la longueur de chaque texte a disposition + generateTextSizeHash(); + + // recupere les longueurs a disposition + QList available_lengths = text_size_hash_.values(); + // trie les longueurs par ordre croissant + qSort(available_lengths.begin(), available_lengths.end()); + // recherche la position ou l'on insererait le rayon + QList::const_iterator i = qUpperBound(available_lengths, radius); + + // la valeur precedent cette position est donc celle qui nous interesse + if (i == available_lengths.begin()) { + // nous sommes au debut de la liste - nous ne pouvons donc pas afficher de chaine + return(QString()); + } else { + -- i; + qreal final_length = *i; + QString final_string = text_size_hash_.keys(final_length).first(); + return(final_string); + } +} + +/** + S'assure que le hash associant les textes utilisables a leur taille soit + correctement rempli. +*/ +void QTextOrientationWidget::generateTextSizeHash() { + QFontMetrics font_metrics(text_font_); + foreach(QString text, text_size_hash_.keys()) { + if (text_size_hash_[text] == -1) { + text_size_hash_[text] = font_metrics.boundingRect(text).width(); + } + } +} + +/** + Determine si une position donnee correspond a un des carres representant un + angle pertinent. + @param pos Position donnee + @param angle_value_ptr Si different de 0, le double pointe par ce parametre + vaudra l'angle pertinent concerne +*/ +bool QTextOrientationWidget::positionIsASquare(const QPointF &pos, double *angle_value_ptr) { + // rectangle de travail avec son centre et son rayon + QRect drawing_rectangle(QPoint(0, 0), size()); + drawing_rectangle.adjust(5, 5, -5, -5); + + QPointF drawing_rectangle_center(drawing_rectangle.center()); + qreal drawing_rectangle_radius = drawing_rectangle.width() / 2.0; + + qreal squares_size = size().width() / 15.0; + qreal square_offset = - squares_size / 2.0; + QRectF square_qrect = QRect(square_offset, square_offset, squares_size, squares_size); + + for (double drawing_angle = 0.0 ; drawing_angle < 360.0 ; drawing_angle += squares_interval_) { + QTransform transform = QTransform() + .translate(drawing_rectangle_center.x(), drawing_rectangle_center.y()) + .rotate(drawing_angle) + .translate(drawing_rectangle_radius - 1.0, 0.0) + .rotate(-45.0); + + QRectF mapped_rectangle = transform.mapRect(square_qrect); + if (mapped_rectangle.contains(pos)) { + if (angle_value_ptr) *angle_value_ptr = drawing_angle; + return(true); + } + } + + return(false); +} diff --git a/sources/qtextorientationwidget.h b/sources/qtextorientationwidget.h new file mode 100644 index 000000000..5374f4aea --- /dev/null +++ b/sources/qtextorientationwidget.h @@ -0,0 +1,91 @@ +/* + Copyright 2006-2010 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech 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. + + QElectroTech 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 QElectroTech. If not, see . +*/ +#ifndef Q_TEXT_ORIENTATION_WIDGET_H +#define Q_TEXT_ORIENTATION_WIDGET_H +#include +/** + Cette classe permet de representer graphiquement l'orientation d'un texte. +*/ +class QTextOrientationWidget : public QWidget { + Q_OBJECT + + // constructeurs, destructeur + public: + QTextOrientationWidget(QWidget * = 0); + virtual ~QTextOrientationWidget(); + private: + QTextOrientationWidget(const QTextOrientationWidget &); + QTextOrientationWidget &operator=(const QTextOrientationWidget &); + + // methodes publiques + public: + double orientation() const; + void setFont(const QFont &); + QFont font() const; + void setDisplayText(bool); + bool textDisplayed() const; + void setUsableTexts(const QStringList &); + QStringList usableTexts() const; + bool isReadOnly() const; + void setReadOnly(bool); + + // slots publics + public slots: + void setOrientation(const double &); + + protected: + virtual QSize sizeHint () const; + int heightForWidth(int) const; + virtual void paintEvent(QPaintEvent *); + void mouseMoveEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + + // signaux + signals: + /** + Signal emis lorsque l'utilisateur specifie une orientation en cliquant + sur le widget + */ + void orientationChanged(double); + + // attributs prives + private: + /// Intervalle entre les petits angles privilegies, en degres + double squares_interval_; + /// angle represente + double current_orientation_; + /// Booleen indiquant s'il faut afficher ou non un texte + bool display_text_; + /// Police utilisee pour le texte affiche + QFont text_font_; + /// Hash associant les textes disponible a leur longueur en pixels + QHash text_size_hash_; + /// Angle specifique a mettre en valeur + double highlight_angle_; + /// Booleen indiquant s'il faut mettre en valeur un des angles + bool must_highlight_angle_; + /// Booleen indiquant si le widget est en mode "lecture seule" ou non + bool read_only_; + + // methodes privees + private: + QString getMostUsableStringForRadius(const qreal &); + void generateTextSizeHash(); + bool positionIsASquare(const QPointF &, double * = 0); +}; +#endif