From 0ea5d6fa4b86609b680267fa464b2c960dd14afe Mon Sep 17 00:00:00 2001 From: karliss Date: Sun, 24 May 2020 01:12:32 +0300 Subject: [PATCH] Add UI for managing layouts (#2211) * Add dialog for deleting and renaming layouts. * Add documentation. Co-authored-by: Itay Cohen --- docs/source/conf.py | 4 + docs/source/images/layout_manager.png | Bin 0 -> 7912 bytes .../user-docs/menus/menu-bar/view-menu.rst | 12 ++- docs/source/user-docs/preferences.rst | 11 +-- docs/source/user-docs/preferences/layout.rst | 21 +++++ src/Cutter.pro | 11 ++- src/common/CutterLayout.cpp | 8 ++ src/common/CutterLayout.h | 25 ++++++ src/core/MainWindow.cpp | 21 +++-- src/core/MainWindow.h | 17 ++-- src/core/MainWindow.ui | 7 ++ src/dialogs/LayoutManager.cpp | 75 ++++++++++++++++++ src/dialogs/LayoutManager.h | 30 +++++++ src/dialogs/LayoutManager.ui | 42 ++++++++++ 14 files changed, 255 insertions(+), 29 deletions(-) create mode 100644 docs/source/images/layout_manager.png create mode 100644 docs/source/user-docs/preferences/layout.rst create mode 100644 src/common/CutterLayout.cpp create mode 100644 src/common/CutterLayout.h create mode 100644 src/dialogs/LayoutManager.cpp create mode 100644 src/dialogs/LayoutManager.h create mode 100644 src/dialogs/LayoutManager.ui diff --git a/docs/source/conf.py b/docs/source/conf.py index 2f5b9f18..411e11f6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -41,6 +41,7 @@ release = '1.10.3' extensions = [ 'breathe', 'recommonmark', + 'sphinx.ext.autosectionlabel' ] # Add any paths that contain templates here, relative to this directory. @@ -73,6 +74,9 @@ exclude_patterns = ['_build'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'rainbow_dash' + +autosectionlabel_prefix_document = True + # -- Options for Breathe ----------------------------------------------------- breathe_projects = { 'cutter': '../doxygen-out/xml' } diff --git a/docs/source/images/layout_manager.png b/docs/source/images/layout_manager.png new file mode 100644 index 0000000000000000000000000000000000000000..ae7d132bc382f945a0dd0a71aeee0a9bf702ba1f GIT binary patch literal 7912 zcma)h^;cWZ^M3FErAYCj#UXf$OQC3SDZz`oJ3))Qu;T6oij_hL?rw$P5*&(CBv{b` zpS=Hp@A=*zcJ4j9bI$DU%sg|SiP2P7B*3G_0{{R7%1UzD000^}>OB#NjT)tOuG~-p z(^FPi7l?WSfmYF|Zz?Z&eJ>qX8!umT4{Ly}i>tFWx2L6twY7_BQypw!?KhjSNQ5&aT*XbL(vp>7G#c>xHw&_pvxbB4Yo*P&*pkNZ^>|bq3@aI1f5)!Zq_^*hp zoFdT=LhzQ^(UwS^)G9HRx9DGfY()!AvM=rkwL19-dDOJ835|E{0h%(O!c%qHU9(7?9UW5_(}`o3{sP}4w*KX=>( zSIydlNrvc0)Q=V^av4pwxXl)^LPO)@u;x>|7_mv8*A3MaV@(Tfs8fq8p^Ovcl7}L| zL}YQ|J0I;hFLsm(_h)#`1mgIBvO$9gMMWoFCWsd@wy?19-G-A;dxM3S z7;O$q1LlyEP(72iaJhE9oGxZjfsXc%vj7Kjai0^|(^I1?odf7jotwO|B~PteGZn!K zlw~HB7pJ1i4mjzjW`E*zl zs!nWldnOSSAtBNTEUe~ilzHM>{OK8DAV-1hpGQTEjBr1OzG8JQ^jg4x z1!nLHVhI6f2WR1u^{HwVHu0NbE6%~q?d{r*UbEU)nG>|)ZYKK)Jd@jSi)rerGWOU9 zesW@RnF^!TMQGw$Q?Di~rz>x8p+w1a4(duKSK>B!c`c=1pTq|Rv`ZvbW4FxU<3rpe z|18vzcDxSn?MaK-_6wPo)#G_i3h8(hYhLvV_kPx?f7b3Trj)c0@hr$ANRxj?(%mUO zonIdXo}WK3OFvRhYb7t#6+T^>v@QR``RckBn_d{CYdXm{^tCPgDJbCb@4ia5kbm($ z&lPo%<%3Vo-Hit2*!JW^za4zo4!%F#&Fc9a0n4!#_jaQ_o1Z}kpJ(8u+8g;e44Pmf zhpYtLrQYRJn3^bQY4JZk{IyxQ!V&Sf60w~uud}}tDpSk0x;a)v9<^VN-$zXt%t%7V(WnOH^jc^C*TOUA-Wz@z!IW8XW`Oeug;*ykWd^?$}9gH&^?`CfLo5 z2-=+lx140fIJ?LHg^7b>pW(c!)c(@Qcu=Qd)Ui$W&*%Wz59z>qAKG&DLKbexaNeiK zcZXEj4UQ|=p(h;exWF^EqCoLC5?_U?4eOj0@w&RYzS2lV$UonPZfF!TqBMlpO;z2@ z7&@Uy(>O4*A2k@whzN8mbnEdW?yj-97FARMvXK>4s&+Y0~+0i6YUYcH zgLkK3DU~Rh3qRsz#?VJJ{>*^mQxb|2SwHITxG)RC#>E#}FEczc*cB{z1wJ zmU0IL?d%}SPez@VJJQ!BM?p#J2sW7g81%gX7?x<7nD_%xG+#I z)W0i1^?Xqk85#4)hc-cLLapTFx31(W|0({M_Z=<0FuO0H(6EB#McH#lO=e6A=~Zei6;H6a?Q^n()6?6p*m9DQ8x!lKYPGGf8hpg< z<85o&^1giFT25YwwP&MSK`QDVe%kr4Kw&KL4! z?)EEy8DRx_Xuw-K~dSnJ*YT)nkZ$yGCRD)|td5NoxXrS;;W-;Bq!ZNSmBQxO$2 zfT#qTQk8PK(_A;gpa7G}f=3+t4W0rrE-;4M{jn&?@(?Qg0_nHJXK(A=+k%`2bcFY*UH$<_9c)}qBP!T(me*)C zwz8r!mQ{qt|H#RxqWf)pOqq8Txf3^%fTSZ%^5JEz?Zy`eXmeeT=5Mm98cxzb!uzu` zR6gLfTo0LwAkGqEva+%U6R@ka)OPP5hY|*$Y2URoc&N?pFpOGW>v&`B7&!Ly{q#aL zn!(u7X`^Z0IllH=re7=r@KbNLk4PXi31Wh&NK~WN@U|zNt|geiiU9?4#OB19Ma%y` z<8)wKs~BHKR(306{@kjCM!qaj#77dc&Hk~@4>1mur_+~ys`_+y9O*AiDE}KVLquZ9 zR1nwUUHaW4#yXb3iu(Fl!xP*5=E~#TLqt@}iReICTG@SiRl_|j56}2@4iNiC0~7+f z6tRcehUC5hg}GI0VRWlZ;^J@GexfXwIP~m^xLn(?TLTm z_Z-r^@;(>C>}6a>;)%8au%_n6H)~4OsJGp`dLHvXC<$j=oc@Qeilnv)`N}XyB!f+R zH=*iKZY}W~^mlc3JcGW>2EFR6V#awoxC6l(Hvi$xU9caiic9o2l?p<3yh(3@-C^-! zJG&O`5GXdb&??pV34ic6_EqZLu_$3-tCKVK3__;?NpAF?);J3Hb-yR*-Dyv|nKD^~1$qa^%V_&CU~g+^4-zM(*L*K(BG*JaRY20$TCS_9}^~TC#>?2mf9}?DgWWxvdXwdA-U3 z0+ghozE^?)8NxmrJ4F9T7I{wlVJTN{L{NM*tdG8s?;Z@0>Iq6vi0 z#m75Q>8|f;PD8hBL}ktZ9EXw2uANFA$p26Rh^n59c*RJ` zzA1D;8^tg5ET8Mwm=0c13{c>Bi1@*>YT_TM0!%RPFYfj7;ro;MeN-eA?{k6D>c;F_ zo4X`oL0$*bzf0<&&EIupv_?VnDqRyz&U&<3#{`5}H!h*=yO_Ng-@j9&bnK$Lu9M>S z{EW|@kjFZ600ZS^+#zVo5AE-8H{UBT($jHy#w=&}9_5T3jd5~VgB{R>g$UoU&ShE$ z)N5&uaD5sP$Z6qOrr(`PcW0rcen`$Sl69%f&La^7oD+9B!9fpaud(hH8x=SxaHTt| zu2(+CFwBvQ3*u!r@|OgzrB`gOk;iTIS>RsYn9Ck9w0Cq;#43+*7kPRC2z`Mp-2?Ce z7lJXa4(*K_whO~HMHt-A2R zz*qe%ktCZX{b0i8hL^BP`h@*7wNlZhAj%aT{KX7|fhBZX7`c07#Img|q-e$3af`kP zhnt+{d6P79s27`+?Oom26JlKjwCbMB1`)E5i*ob8`)J_Jm;3vqANGg_)!^PdClPF) z*f**s3f*P9m?}COj&KuG#PN`7&dLm#*lfSFmXwq)#pC^#_vH&^!5_x}y_0{I)zw-N3awAHJGk*28A}202Vgx~dncv~tJi=(;TVX)i z-E*6`Qm1Iezn^;sx3sZ?gNv7s$RZpgb8dZ)muw?3Whw&v>IcLAwqnW<{UJ5w1_^Y1 zAo{~@3Ie_ROIpeROfO-Tz)i$e(T(P%z`LBj!-4!EMP+Cb&*Wqqf-v@gRfH!BE1rBD z9c`fE%FB^)1!9pXt?veMM7-CIACSil5TH>c#z0>6y}IRu7Km)ImEQDwkvgjbg1$tUxzJ3 z5m6jD%dhlsjby7a0BLDWzgrah?Vzlov40!0JkUF<8kh!j_x- zZhiihSV@BjaRd{m&k^?$2Z8_ZHxx;?sZ24~NuKBK;?~Ekx7*Ck5M&SO`hxJ+Rzom|UK`oJ$mPVSG-)~fD z%b!tq>3z8R>%Q>EbG&d;L3VE}Z}(2q3O2$m;19nyoFF##>vzROx`mUX4^cCnM!u$y zJL@)|laDJ+&Wzs|(i5ia@&C02)R@T-Am#FHs3h~o?{>O7ta&j-d%WL>R z%qv_10v0Z=xG&8wS?^j!o!09jP1=2CtH2s%8o3IyWol@PE$&Pkfe+jP*T*c(%vme0 z-TCg8!z4sRfS$sqf40~J)B|^(`?uDYyFu3NF3cpofhg@QxI zj}7Y4l+@IA`w6A3#ulm@9OhGSx7qZuetC3QTtYVg{R_TwdfT=U(4LB<^f1O!{XHyMWFUn|<#RqRX0iG|tj zTUK(v>I&?f(;bDC4C{A72>Snf(YVKJa*taW@`QDxmN#;oEx6SE47l8rOtjE@&+)2Y zKV_ePa}Q18>7XP}H0VFJ0zVR+7+g{jj~(F2a?^HpdBIj!Osam{)hnqdzsklZ3>?Dq zg%xB{)?#vbL10Qx==&$HNqtt;Jh^VEApeW=;a{V0i-luF4jxWyET7ZGUvPu^R`025 zc%d+1h;&GqYEWq2ah}*A_B^q&du*>614kuJ6d^74=GI`GYTnzJ#YO#;{fSaVZNf6i ze|$KEbaN8xs9hiDiRNiYPHrbkPUJHwoShG=7_@q@pt1l6^56_$T?^nFnGpTQd9=wnzB_+eZ2V_#x^g8mWhe zhu=}Y@9kM3a<`MJGh)Fa|wK`iW(V;7Y#V20XQ!F zWaNio+^h%i0+f}NX`}+P{vIJxv$FJmFQU8~=08Th05wu{bK^tJW~s7fgj`dVC?+=1 zrKY9HJzSrljkMe z+HqrATwJVwzMI+c+>3KvleI8oV`5wR3GH(@FWS@7s;C9#E7q^gjyit&evbl-4oNdJ z+S)#)ZeyWFp>-4o@8aTj^F1hr!dQgLSVcrAL&*w55_;Ws%Zq0Zrw$u7f@mo9uWK$S z_9vlcSn>)AdMC0SM1+J%RouCyPMzLIdJ4LM(*9?P3l|c8M$p0qufHv zPbza#8$ma;R8-k7=%r{vuTNGk9|@(2C@Gb~u?dXnHhz5K0tg5SDu4XwpJWk$1U2DQGwKt*Qfka&w+Pqphz6W@*s+M;c^$;kzd#92FW0pbNOt+JmbNO-?4B zuC*s;W)4^Cl(!t!v>Pu_P*ckqJAuYBLjE(*8W|h2eAJLO@fK&>2$A-EzN0fWtPL&t z@ZY!Dqor0$Hp}7l@b2#CPl(SO|H~hU8WR+Oqo+r{;(Kg{%G@1STXA|fK4+V~f!ENG zbGYCnABu$Q>+2JNT(Rei`<9_d1Wc@SgZg$~WEm=^nVFg0hUud&S65r%Iijg8Eg~~B zGkmwy2Z(0+-rn8@yY;+AM+6BSxqAP91oq1f9D=rRv!^)Tt)qQK#8FMZ-CK|QD|>|m z8iQPVt@tgNDdQL_Oy-NsmtNu$?8Xe!Z&#&rEp=Jq;90LFxpuZDId?yehR>E?PP8Mn zGMAgWYMPH5I2^&2sN}De&^7Sx!;@{E)41s@h1{v9+h? zeFn%>)xzS9>(hy=#NC|E_v2;j_)HYmd)ypMcib;K>Lo*tr-!rnf+e`R{-r3|Ojyl_ zJn4{2gPxQ2q@N`W3@H3uPK{CS5LL{+8vJn5aX&idw9%EXXftl{Yay|DCR#cBSTM#HT73ppxv@pGuL{Dzo78n?&)wf3W>p?XXj%1xQu}zA#Z&f z)WNR0^%J~X+AI<3o|qmL@+^O`(cV#P;@fxO(~JjtIHy zSs#}3&N?nQ3hvLP8zYdZd|E5KHQ#GAc&~USw9|wOjw`EO^zk)Zp#8fdE|)eKki5dW z_^lMJ=Bno#kET7Edsv|K5r3>U6O#xQp{Cl+c(xB^175}D4_2XA_Bh1WdVK0iz{%!E z!a09zTXE+o9pt2hM5`Q(EdH;?=Zv%+}!I|0Foxw9x1&9K+Ib#hH%6)3c*`(_h zIcKBsi97rDwuweM`0GZ(KfTiJ$Yn-m<}{n}t+Pu{DN(PemnWiux55c@l6oJwU;PNL zozgUHayYqoawuBBw7uVSqlpvK*D?5lOGwBj)y3V~sxZKD!RmE;cSnM%q8oJOtC$=L+$NV2dy|`Fl9D{MLo&6W4wc2mZVh)j>k(of1LcV% z;xa1q7fe`uBjrq*bMVOV%^EeG5RDn+XxccAYu;bv^f8O>-L0xCGAAr}Y6b?m<4Og# zihbN^upH$zF^hcMS@=#3c~4_wxff1ts)s5D<++)gGVnHab(zcF0N+LF!3VQj7G1>a z6&j4+RC}Ua&uN>@jF-n@?0XxCjF?KM1wu0@QM2FyOPhtbOlcXEBgyK_9IF>Iyx#(A z>P{2?cXN^xVU-C3@?)q@pqJB)qdA2o?1Z4sq&ZE?h_%T-!*cZyjf9aHbFzem)$qtr z_V-7nyy|cVC23t!(&{0Hn7Cd2l~Tx>LBappehbLObR#PRvAAC?qZpho4}Is}H&Rw$ z$#bid4`7i*` to the default layout provided by Cutter. **Steps:** View -> Reset to default layout @@ -67,14 +67,20 @@ Reset Zoom **Shortcut:** :kbd:`Ctrl` + :kbd:`=` +Manage layouts +---------------------------------------- +**Description:** Rename and delete saved :doc:`layouts`. + +**Steps:** View -> Manage layouts , select layout, choose command + Save layout ---------------------------------------- -**Description:** Save the current layout with a given name. A layout includes the set of currently opened widgets, their position, and some properties. +**Description:** Save the current :doc:`layout` with a given name. A layout includes the set of currently opened widgets, their position, and some properties. **Steps:** View -> Save Layout , enter a layout name in the dialog. Layouts ---------------------------------------- -**Description:** Load the settings from the selected layout into the current layout. Loading a layout will not cause it to automatically be modified. To do that you must use the `Save layout`_ command. +**Description:** Load the settings from the selected :doc:`layout` into the current layout. Loading a layout will not cause it to automatically be modified. To do that you must use the `Save layout`_ command. **Steps:** View -> Layouts -> layout name diff --git a/docs/source/user-docs/preferences.rst b/docs/source/user-docs/preferences.rst index 7189df32..ac38a906 100644 --- a/docs/source/user-docs/preferences.rst +++ b/docs/source/user-docs/preferences.rst @@ -1,11 +1,12 @@ -Preferences +Configuration ===================== -This part of the documentation will provide the reader with information about different preferences available. -Preferences can be opened by clicking ``Edit -> Preferences``. +This part of the documentation will provide the reader with information about different configuration options available. +Most configuration is done using the Preferences dialog. It can be opened by clicking :ref:`Edit -> Preferences`. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :glob: preferences/initialization-script - preferences/* \ No newline at end of file + preferences/layout + preferences/* diff --git a/docs/source/user-docs/preferences/layout.rst b/docs/source/user-docs/preferences/layout.rst new file mode 100644 index 00000000..cf6ae295 --- /dev/null +++ b/docs/source/user-docs/preferences/layout.rst @@ -0,0 +1,21 @@ +Layout +====== + +The set of currently opened widgets, their placement, and some properties is grouped into layouts. +Cutter will automatically restore the last layout state when reopening Cutter. Last debug and normal layouts are stored separately. +You can :ref:`save` multiple named layouts for different use cases. +Use :doc:`../menus/menu-bar/view-menu` to :ref:`save`, +:ref:`load` or :ref:`user-docs/menus/menu-bar/view-menu:Reset to default layout`. +A named layout is never automatically modified. To modify a previously saved layout, instead of entering a new name, select +an existing layout from list in Save Layout dialog. + +Layout manager +----------------------------------- + +.. image:: ../../images/layout_manager.png + :alt: Layout manager dialog + + +**Description:** Layout manager allows renaming and deleting saved layouts. + +**Steps to open:** :doc:`View<../menus/menu-bar/view-menu>` -> :ref:`user-docs/menus/menu-bar/view-menu:Manage Layouts` diff --git a/src/Cutter.pro b/src/Cutter.pro index 03a016ed..950dfea7 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -422,7 +422,9 @@ SOURCES += \ dialogs/MultitypeFileSaveDialog.cpp \ widgets/BoolToggleDelegate.cpp \ common/IOModesController.cpp \ - common/SettingsUpgrade.cpp + common/SettingsUpgrade.cpp \ + dialogs/LayoutManager.cpp \ + common/CutterLayout.cpp GRAPHVIZ_SOURCES = \ widgets/GraphvizLayout.cpp @@ -570,7 +572,9 @@ HEADERS += \ dialogs/MultitypeFileSaveDialog.h \ widgets/BoolToggleDelegate.h \ common/IOModesController.h \ - common/SettingsUpgrade.h + common/SettingsUpgrade.h \ + dialogs/LayoutManager.h \ + common/CutterLayout.h GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h @@ -637,7 +641,8 @@ FORMS += \ dialogs/LinkTypeDialog.ui \ widgets/ColorPicker.ui \ dialogs/preferences/ColorThemeEditDialog.ui \ - widgets/ListDockWidget.ui + widgets/ListDockWidget.ui \ + dialogs/LayoutManager.ui RESOURCES += \ resources.qrc \ diff --git a/src/common/CutterLayout.cpp b/src/common/CutterLayout.cpp new file mode 100644 index 00000000..4dc145b6 --- /dev/null +++ b/src/common/CutterLayout.cpp @@ -0,0 +1,8 @@ +#include "CutterLayout.h" + +using namespace Cutter; + +bool Cutter::isBuiltinLayoutName(const QString &name) +{ + return name == LAYOUT_DEFAULT || name == LAYOUT_DEBUG; +} diff --git a/src/common/CutterLayout.h b/src/common/CutterLayout.h new file mode 100644 index 00000000..b817bf80 --- /dev/null +++ b/src/common/CutterLayout.h @@ -0,0 +1,25 @@ +#ifndef CUTTER_LAYOUT_H +#define CUTTER_LAYOUT_H + +#include +#include +#include +#include + +namespace Cutter +{ + +struct CutterLayout +{ + QByteArray geometry; + QByteArray state; + QMap viewProperties; +}; + +const QString LAYOUT_DEFAULT = "Default"; +const QString LAYOUT_DEBUG = "Debug"; + +bool isBuiltinLayoutName(const QString &name); + +} +#endif diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 5371d1c0..a6b08de7 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -26,6 +26,7 @@ #include "dialogs/preferences/PreferencesDialog.h" #include "dialogs/MapFileDialog.h" #include "dialogs/AsyncTaskDialog.h" +#include "dialogs/LayoutManager.h" // Widgets Headers #include "widgets/DisassemblerGraphView.h" @@ -114,12 +115,7 @@ template T *getNewInstance(MainWindow *m) { return new T(m); } -static const QString LAYOUT_DEFAULT = "Default"; -static const QString LAYOUT_DEBUG = "Debug"; - -static bool isBuiltinLayoutName(const QString &name) { - return name == LAYOUT_DEFAULT || name == LAYOUT_DEBUG; -} +using namespace Cutter; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -235,6 +231,7 @@ void MainWindow::initUI() }); connect(ui->actionSaveLayout, &QAction::triggered, this, &MainWindow::saveNamedLayout); + connect(ui->actionManageLayouts, &QAction::triggered, this, &MainWindow::manageLayouts); /* Setup plugins interfaces */ for (auto &plugin : Plugins()->getPlugins()) { @@ -1110,11 +1107,14 @@ void MainWindow::saveNamedLayout() { bool ok = false; QString name; + QStringList names = layouts.keys(); + names.removeAll(LAYOUT_DEBUG); + names.removeAll(LAYOUT_DEFAULT); while (name.isEmpty() || isBuiltinLayoutName(name)) { if (ok) { QMessageBox::warning(this, tr("Save layout error"), tr("'%1' is not a valid name.").arg(name)); } - name = QInputDialog::getText(this, tr("Save layout"), tr("Enter name"), QLineEdit::Normal, {}, &ok); + name = QInputDialog::getItem(this, tr("Save layout"), tr("Enter name"), names, -1, true, &ok); if (!ok) { return; } @@ -1124,6 +1124,13 @@ void MainWindow::saveNamedLayout() saveSettings(); } +void MainWindow::manageLayouts() +{ + LayoutManager layoutManger(layouts, this); + layoutManger.exec(); + updateLayoutsMenu(); +} + void MainWindow::addWidget(CutterDockWidget *widget) { dockWidgets.push_back(widget); diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index 34edd0d6..b86a7469 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -7,6 +7,7 @@ #include "common/Configuration.h" #include "common/InitialOptions.h" #include "common/IOModesController.h" +#include "common/CutterLayout.h" #include "MemoryDockWidget.h" #include @@ -56,13 +57,6 @@ namespace Ui { class MainWindow; } -struct CutterLayout -{ - QByteArray geometry; - QByteArray state; - QMap viewProperties; -}; - class MainWindow : public QMainWindow { Q_OBJECT @@ -279,7 +273,7 @@ private: QMenu *disassemblyContextMenuExtensions = nullptr; QMenu *addressableContextMenuExtensions = nullptr; - QMap layouts; + QMap layouts; void initUI(); void initToolBar(); @@ -287,10 +281,10 @@ private: void initBackForwardMenu(); void displayInitialOptionsDialog(const InitialOptions &options = InitialOptions(), bool skipOptionsDialog = false); - CutterLayout getViewLayout(); - CutterLayout getViewLayout(const QString &name); + Cutter::CutterLayout getViewLayout(); + Cutter::CutterLayout getViewLayout(const QString &name); - void setViewLayout(const CutterLayout &layout); + void setViewLayout(const Cutter::CutterLayout &layout); void loadLayouts(QSettings &settings); void saveLayouts(QSettings &settings); @@ -313,6 +307,7 @@ private: void updateHistoryMenu(QMenu *menu, bool redo = false); void updateLayoutsMenu(); void saveNamedLayout(); + void manageLayouts(); void setOverviewData(); bool isOverviewActive(); diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index f1644f1e..9500ac12 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -114,6 +114,8 @@ + + @@ -849,6 +851,11 @@ Save layout + + + Manage layouts + + diff --git a/src/dialogs/LayoutManager.cpp b/src/dialogs/LayoutManager.cpp new file mode 100644 index 00000000..c8e1bb76 --- /dev/null +++ b/src/dialogs/LayoutManager.cpp @@ -0,0 +1,75 @@ +#include "LayoutManager.h" +#include "ui_LayoutManager.h" +#include +#include + +using namespace Cutter; + +LayoutManager::LayoutManager(QMap &layouts, QWidget *parent) : + QDialog(parent), + ui(new Ui::LayoutManager), + layouts(layouts) +{ + ui->setupUi(this); + connect(ui->renameButton, &QPushButton::clicked, this, &LayoutManager::renameCurrentLayout); + connect(ui->deleteButton, &QPushButton::clicked, this, &LayoutManager::deleteLayout); + connect(ui->layoutSelector, &QComboBox::currentTextChanged, this, &LayoutManager::updateButtons); + refreshNameList(); +} + +LayoutManager::~LayoutManager() +{ +} + +void LayoutManager::refreshNameList(QString selection) +{ + ui->layoutSelector->clear(); + for (auto it = layouts.begin(), end = layouts.end(); it != end; ++it) { + if (!Cutter::isBuiltinLayoutName(it.key())) { + ui->layoutSelector->addItem(it.key()); + } + } + if (!selection.isEmpty()) { + ui->layoutSelector->setCurrentText(selection); + } + updateButtons(); +} + +void LayoutManager::renameCurrentLayout() +{ + QString current = ui->layoutSelector->currentText(); + if (layouts.contains(current)) { + QString newName; + while (newName.isEmpty() || isBuiltinLayoutName(newName) || layouts.contains(newName)) { + if (!newName.isEmpty()) { + QMessageBox::warning(this, tr("Rename layout error"), tr("'%1' is already used.").arg(newName)); + } + newName = QInputDialog::getText(this, tr("Save layout"), tr("Enter name"), QLineEdit::Normal, + current); + if (newName.isEmpty()) { + return; + } + } + auto layout = layouts.take(current); + layouts.insert(newName, layout); + refreshNameList(newName); + } +} + +void LayoutManager::deleteLayout() +{ + auto selected = ui->layoutSelector->currentText(); + auto answer = QMessageBox::question(this, tr("Delete"), + tr("Do you want to delete '%1'").arg(selected)); + if (answer == QMessageBox::Yes) { + layouts.remove(selected); + refreshNameList(); + } +} + +void LayoutManager::updateButtons() +{ + bool hasSelection = !ui->layoutSelector->currentText().isEmpty(); + ui->renameButton->setEnabled(hasSelection); + ui->deleteButton->setEnabled(hasSelection); +} diff --git a/src/dialogs/LayoutManager.h b/src/dialogs/LayoutManager.h new file mode 100644 index 00000000..cac19103 --- /dev/null +++ b/src/dialogs/LayoutManager.h @@ -0,0 +1,30 @@ +#ifndef LAYOUT_MANAGER_H +#define LAYOUT_MANAGER_H + +#include +#include +#include "core/Cutter.h" +#include "common/CutterLayout.h" + +namespace Ui { +class LayoutManager; +} + +class LayoutManager : public QDialog +{ + Q_OBJECT + +public: + LayoutManager(QMap &layouts, QWidget *parent); + ~LayoutManager(); + +private: + void refreshNameList(QString selection = ""); + void renameCurrentLayout(); + void deleteLayout(); + void updateButtons(); + std::unique_ptr ui; + QMap &layouts; +}; + +#endif // LAYOUT_MANAGER_H diff --git a/src/dialogs/LayoutManager.ui b/src/dialogs/LayoutManager.ui new file mode 100644 index 00000000..1eadc263 --- /dev/null +++ b/src/dialogs/LayoutManager.ui @@ -0,0 +1,42 @@ + + + LayoutManager + + + + 0 + 0 + 254 + 88 + + + + Layout + + + + + + + + + + + Rename + + + + + + + Delete + + + + + + + + + +