Salome HOME
Copyright update 2022
[modules/gui.git] / src / Qtx / QtxShortcutEdit.cxx
1 // Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "QtxShortcutEdit.h"
21
22 #include <QWidget>
23 #include <QLayout>
24 #include <QList>
25
26 #include <QToolButton>
27 #include <QLineEdit>
28 #include <QTableWidgetItem>
29 #include <QMessageBox>
30
31 #include <QKeyEvent>
32 #include <QKeySequence>
33
34 #define COLUMN_SIZE  500
35
36 static const char* delete_icon[] = {
37 "16 16 3 1",
38 "` c #810000",
39 "  c none",
40 "# c #ffffff",
41 "                ",
42 "                ",
43 " ``#        ``# ",
44 " ````#     ``#  ",
45 "  ````#   ``#   ",
46 "    ```# `#     ",
47 "     `````#     ",
48 "      ```#      ",
49 "     `````#     ",
50 "    ```# ``#    ",
51 "   ```#   ``#   ",
52 "  ```#     `#   ",
53 "  ```#      `#  ",
54 "   `#        `# ",
55 "                ",
56 "                "
57 };
58
59 /*!
60   \brief Constructor
61   \param parent parent widget
62 */
63 QtxShortcutEdit::QtxShortcutEdit( QWidget* parent )
64 : QFrame( parent )
65 {
66   initialize();
67   myShortcut->installEventFilter(this);
68 }
69
70 /*!
71   \brief Destructor
72 */
73 QtxShortcutEdit::~QtxShortcutEdit()
74 {
75 }
76
77 /*!
78   \brief Sets custom shortcut
79   \param seq a key sequence describes a combination of keys
80   \sa shortcut()
81 */
82 void QtxShortcutEdit::setShortcut( const QKeySequence& seq )
83 {
84   QString txt = seq.toString(); 
85   myPrevShortcutText = txt;
86   myShortcut->setText( txt ); 
87 }
88
89 /*!
90   \brief Gets custom shortcut 
91   \return a key sequence describes a combination of keys
92   \sa setShortcut()
93 */
94 QKeySequence QtxShortcutEdit::shortcut()
95 {
96   return QKeySequence::fromString( myShortcut->text() );
97 }
98
99 /*!
100   \brief Gets the key sequence from keys that were pressed 
101   \param e a key event
102   \return a string representation of the key sequence
103 */
104 QString QtxShortcutEdit::parseEvent( QKeyEvent* e )
105 {
106   bool isShiftPressed = e->modifiers() & Qt::ShiftModifier;
107   bool isControlPressed = e->modifiers() & Qt::ControlModifier;
108   bool isAltPressed = e->modifiers() & Qt::AltModifier;
109   bool isMetaPressed = e->modifiers() & Qt::MetaModifier;
110   bool isModifiersPressed = isShiftPressed || isControlPressed || isAltPressed || isMetaPressed;
111   int result=0;
112   if( isControlPressed )
113     result += Qt::CTRL;
114   if( isAltPressed )
115     result += Qt::ALT;
116   if( isShiftPressed )
117     result += Qt::SHIFT;
118   if( isMetaPressed )
119     result += Qt::META;
120
121   int aKey = e->key();
122   if ( ( isValidKey( aKey ) && isModifiersPressed ) || ( ( aKey >= Qt::Key_F1 ) && ( aKey <= Qt::Key_F12 ) ) )
123     result += aKey;
124
125   return QKeySequence( result ).toString();
126 }
127
128 /*!
129   \brief Check if the key event contains a 'valid' key
130   \param aKey the code of the key
131   \return \c true if the key is 'valid'
132 */
133
134 bool QtxShortcutEdit::isValidKey( int aKey )
135 {
136   if ( aKey == Qt::Key_Underscore || aKey == Qt::Key_Escape || 
137      ( aKey >= Qt::Key_Backspace && aKey <= Qt::Key_Delete ) || 
138      ( aKey >= Qt::Key_Home && aKey <= Qt::Key_PageDown ) || 
139      ( aKey >= Qt::Key_F1 && aKey <= Qt::Key_F12 )  ||
140      ( aKey >= Qt::Key_Space && aKey <= Qt::Key_Asterisk ) ||
141      ( aKey >= Qt::Key_Comma && aKey <= Qt::Key_Question ) ||
142      ( aKey >= Qt::Key_A && aKey <= Qt::Key_AsciiTilde ) )
143     return true;
144   return false;
145 }
146
147 /*!
148   \brief Called when "Clear" button is clicked.
149 */
150 void QtxShortcutEdit::onCliked() 
151 {
152   myShortcut->setText( "" );
153 }
154
155 /*!
156   \brief Called when myShortcut loses focus.
157 */
158 void QtxShortcutEdit::onEditingFinished() 
159 {
160   if ( myShortcut->text().endsWith("+") )
161     myShortcut->setText( myPrevShortcutText );
162 }
163
164 /*!
165   \brief Custom event filter.
166   \param obj event receiver object
167   \param event event
168   \return \c true if further event processing should be stopped
169 */
170 bool QtxShortcutEdit::eventFilter(QObject* obj, QEvent* event) 
171
172   if ( obj == myShortcut ) { 
173     if (event->type() == QEvent::KeyPress ) {
174       QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
175       QString text = parseEvent( keyEvent );
176       if ( keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace )
177         onCliked();
178       if ( text != "" )
179         myShortcut->setText( text );
180       return true;
181     }
182     if ( event->type() == QEvent::KeyRelease ) {
183       if ( myShortcut->text().endsWith("+") )
184         myShortcut->setText( myPrevShortcutText );
185       else myPrevShortcutText = myShortcut->text();
186
187       return true;
188     }
189   } 
190   return false;
191 }
192
193 /*
194   \brief Perform internal intialization.
195 */
196 void QtxShortcutEdit::initialize()
197 {
198   myPrevShortcutText = QString();
199
200   QHBoxLayout* base = new QHBoxLayout( this );
201   base->setMargin( 0 );
202   base->setSpacing( 5 );
203
204   base->addWidget( myShortcut = new QLineEdit( this ) );
205
206   QToolButton* deleteBtn = new QToolButton();
207   deleteBtn->setIcon( QPixmap( delete_icon ) );
208   base->addWidget( deleteBtn );
209  
210   myShortcut->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
211   deleteBtn->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
212
213   connect( deleteBtn, SIGNAL( clicked() ), this, SLOT( onCliked() ) );
214   connect( myShortcut, SIGNAL( editingFinished() ), this, SLOT( onEditingFinished() ) );
215 }
216
217 /*!
218   \brief Constructor
219   \param parent parent widget
220 */
221 QtxShortcutTree::QtxShortcutTree( QWidget * parent ) : QTreeWidget( parent )
222 {
223   setColumnCount( 2 );
224   setSelectionMode( QAbstractItemView::SingleSelection );
225   setColumnWidth ( 0, COLUMN_SIZE);
226   setSortingEnabled(false);
227   headerItem()->setHidden ( true ); 
228
229   this->installEventFilter(this);
230   connect( this, SIGNAL( currentItemChanged ( QTreeWidgetItem*, QTreeWidgetItem* ) ), this, SLOT( onCurrentItemChanged ( QTreeWidgetItem*, QTreeWidgetItem* ) ) );
231
232 }
233
234 /*!
235   \brief Destructor
236 */
237 QtxShortcutTree::~QtxShortcutTree(){}
238
239 /*!
240   \brief Custom event filter. 
241   \param obj event receiver object
242   \param event event
243   \return \c true if further event processing should be stopped
244 */
245 bool QtxShortcutTree::eventFilter(QObject* /*obj*/, QEvent* event) 
246
247   if ( currentItem() && currentItem()->isSelected() ) {
248     
249     if (event->type() == QEvent::KeyPress ) {
250       QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
251       QString text = QtxShortcutEdit::parseEvent( keyEvent );
252       if ( keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace )
253         currentItem()->setText( 1, "" );
254       if ( text != "" ) {
255         if ( text.endsWith( "+" ) || checkUniqueness( currentItem(), text ) )
256            currentItem()->setText( 1, text );
257       }
258       return true;
259     }
260     if ( event->type() == QEvent::KeyRelease ) {
261       if ( currentItem()->text( 1 ).endsWith( "+" ) )
262         currentItem()->setText( 1, myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] );
263       else myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] = currentItem()->text( 1 );
264
265       return true;
266     }    
267   } 
268   return false;
269 }
270
271 /*!
272   \brief Called when the current item changes.
273   \param cur the current item
274   \param prev the previous current item
275 */
276 void QtxShortcutTree::onCurrentItemChanged( QTreeWidgetItem* /*cur*/, QTreeWidgetItem* prev )
277 {
278   if ( prev && prev->text( 1 ).endsWith( "+" ) )
279       prev->setText( 1, myPrevBindings[ prev->parent()->text( 0 ) ][ prev->text( 0 ) ] );
280 }
281
282 /*!
283   \brief Set key bindings to the tree
284   \param title the name of top-level item
285   \param theShortcutMap map of key bindings
286 */
287 void QtxShortcutTree::setBindings( const QString& title, const ShortcutMap& theShortcutMap )
288 {
289   QTreeWidgetItem* item= new QTreeWidgetItem();
290   QFont font = item->font(0);
291   font.setBold(true);
292   
293   if ( findItems( title, Qt::MatchFixedString ).isEmpty()  ) {
294     item->setText( 0, title );
295     item->setFont( 0, font );
296     addTopLevelItem( item );
297     item->setFlags( Qt::ItemIsEnabled );
298   } else {
299     item = findItems( title, Qt::MatchFixedString ).first();
300     item->takeChildren();
301   }
302   for( ShortcutMap::const_iterator it = theShortcutMap.constBegin(); it != theShortcutMap.constEnd(); ++it )
303       item->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) );
304   myPrevBindings.insert( title, theShortcutMap);
305 }
306
307 /*!
308   \brief Get all sections names.
309   \return list of section names
310 */
311 QStringList QtxShortcutTree::sections() const
312 {
313   QStringList lst;
314   for( int i = 0; i < topLevelItemCount(); i++ )
315     lst << topLevelItem( i )->text( 0 ); 
316   return lst;
317 }
318
319 ShortcutMap* QtxShortcutTree::bindings( const QString& sec ) const
320 {
321   ShortcutMap* aMap = new ShortcutMap();
322   QTreeWidgetItem* item = findItems( sec, Qt::MatchFixedString ).first();
323   int nbChildren = item->childCount();
324
325   for( int i = 0; i < nbChildren; i++ ) {
326     QTreeWidgetItem* child =  item->child(i);
327     aMap->insert( child->text( 0 ), child->text( 1 ) );
328   }
329
330   return aMap;
331 }
332
333 void QtxShortcutTree::focusOutEvent ( QFocusEvent* event )
334 {
335   QWidget::focusOutEvent( event );
336   if ( currentItem() && currentItem()->isSelected() )
337     currentItem()->setText( 1, myPrevBindings[ currentItem()->parent()->text( 0 ) ][ currentItem()->text( 0 ) ] );
338 }
339
340 /*!
341   \brief Set the list of shortcuts general sections.
342   
343   Key combinations in general sections should not intersect
344   with any other key combinations.
345
346   \param sectionsList list of common section names
347 */
348 void QtxShortcutTree::setGeneralSections( const QStringList& sectionsList )
349 {
350   myGeneralSections = sectionsList;
351 }
352
353 /*!
354   \brief Check uniqueness of the shortcut.
355   \param item current item of the shortcut tree
356   \param shortcut shortcut appointed for the current item
357   \return \c true if the given shortcut is allowed
358 */
359 bool QtxShortcutTree::checkUniqueness( QTreeWidgetItem* item, const QString& shortcut )
360 {
361   // List of sections to check shortcut intersections
362   QStringList sectionsList;
363
364   // Current section
365   QString currentSection = currentItem()->parent()->text( 0 );
366
367   // If the current section is general 
368   if ( myGeneralSections.contains(currentSection) ) {
369     sectionsList = sections();
370     int currentSectionIndex = sectionsList.indexOf(currentSection);
371     sectionsList.move( currentSectionIndex, 0);
372   } 
373   else {
374     sectionsList = myGeneralSections;
375     sectionsList.prepend(currentSection);
376   }
377
378   // Iterate on sections
379   QStringList::const_iterator it;
380   for( it = sectionsList.constBegin(); it != sectionsList.constEnd(); ++it ) {
381     QString section = *it;
382
383     // Iterate on actual section
384     QTreeWidgetItem* sectionRoot = findItems( section, Qt::MatchFixedString ).first();
385     int nbChildren = sectionRoot->childCount();
386
387     for( int i = 0; i < nbChildren; i++ ) {
388       QTreeWidgetItem* child =  sectionRoot->child(i);
389       
390       if ( (child != item) && (shortcut == child->text( 1 )) ) {
391         bool res = QMessageBox::warning( parentWidget(), tr("Warning"), 
392                                          tr("The \"%1\" shortcut has already used by the \"%2\" action.\n")
393                                          .arg(shortcut, section + ":" + child->text( 0 ) ) +
394                                          tr("Do you want to reassign it from that action to the current one?"),
395                                          QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes;
396         if (res) 
397           child->setText( 1, "" );
398         return res;     
399       }
400     }
401   }
402
403   return true;
404 }