dialog.c 148 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425
  1. /*
  2. * dialog.c - GTK implementation of the PuTTY configuration box.
  3. */
  4. #include <assert.h>
  5. #include <stdarg.h>
  6. #include <ctype.h>
  7. #include <time.h>
  8. #include <gtk/gtk.h>
  9. #if !GTK_CHECK_VERSION(3,0,0)
  10. #include <gdk/gdkkeysyms.h>
  11. #endif
  12. #define MAY_REFER_TO_GTK_IN_HEADERS
  13. #include "putty.h"
  14. #include "gtkcompat.h"
  15. #include "columns.h"
  16. #include "unifont.h"
  17. #include "gtkmisc.h"
  18. #ifndef NOT_X_WINDOWS
  19. #include <gdk/gdkx.h>
  20. #include <X11/Xlib.h>
  21. #include <X11/Xutil.h>
  22. #include "x11misc.h"
  23. #endif
  24. #include "storage.h"
  25. #include "dialog.h"
  26. #include "tree234.h"
  27. #include "licence.h"
  28. #include "ssh.h"
  29. #if GTK_CHECK_VERSION(2,0,0)
  30. /* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
  31. #define USE_GTK_FILE_CHOOSER_DIALOG
  32. #endif
  33. struct Shortcut {
  34. GtkWidget *widget;
  35. struct uctrl *uc;
  36. int action;
  37. };
  38. struct Shortcuts {
  39. struct Shortcut sc[128];
  40. };
  41. struct selparam;
  42. struct uctrl {
  43. dlgcontrol *ctrl;
  44. GtkWidget *toplevel;
  45. GtkWidget **buttons; int nbuttons; /* for radio buttons */
  46. GtkWidget *entry; /* for editbox, filesel, fontsel */
  47. GtkWidget *button; /* for filesel, fontsel */
  48. #if !GTK_CHECK_VERSION(2,4,0)
  49. GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */
  50. GtkWidget *menu; /* for optionmenu (==droplist) */
  51. GtkWidget *optmenu; /* also for optionmenu */
  52. #else
  53. GtkWidget *combo; /* for combo box (either editable or not) */
  54. #endif
  55. #if GTK_CHECK_VERSION(2,0,0)
  56. GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */
  57. GtkListStore *listmodel; /* for all types of list box */
  58. #endif
  59. GtkWidget *text; /* for text */
  60. GtkWidget *label; /* for dlg_label_change */
  61. GtkAdjustment *adj; /* for the scrollbar in a list box */
  62. struct selparam *sp; /* which switchable pane of the box we're in */
  63. guint textsig;
  64. int nclicks;
  65. const char *textvalue; /* temporary, for button-only file selectors */
  66. };
  67. struct dlgparam {
  68. tree234 *byctrl, *bywidget;
  69. void *data;
  70. struct {
  71. unsigned char r, g, b; /* 0-255 */
  72. bool ok;
  73. } coloursel_result;
  74. /* `flags' are set to indicate when a GTK signal handler is being called
  75. * due to automatic processing and should not flag a user event. */
  76. int flags;
  77. struct Shortcuts *shortcuts;
  78. GtkWidget *window, *cancelbutton;
  79. dlgcontrol *currfocus, *lastfocus;
  80. #if !GTK_CHECK_VERSION(2,0,0)
  81. GtkWidget *currtreeitem, **treeitems;
  82. int ntreeitems;
  83. #else
  84. size_t nselparams;
  85. struct selparam **selparams;
  86. #endif
  87. struct selparam *curr_panel;
  88. struct controlbox *ctrlbox;
  89. int retval;
  90. post_dialog_fn_t after;
  91. void *afterctx;
  92. };
  93. #define FLAG_UPDATING_COMBO_LIST 1
  94. #define FLAG_UPDATING_LISTBOX 2
  95. enum { /* values for Shortcut.action */
  96. SHORTCUT_EMPTY, /* no shortcut on this key */
  97. SHORTCUT_TREE, /* focus a tree item */
  98. SHORTCUT_FOCUS, /* focus the supplied widget */
  99. SHORTCUT_UCTRL, /* do something sane with uctrl */
  100. SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */
  101. SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */
  102. };
  103. #if GTK_CHECK_VERSION(2,0,0)
  104. enum {
  105. TREESTORE_PATH,
  106. TREESTORE_PARAMS,
  107. TREESTORE_NUM
  108. };
  109. #endif
  110. /*
  111. * Forward references.
  112. */
  113. static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
  114. gpointer data);
  115. static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
  116. int chr, int action, void *ptr);
  117. static void shortcut_highlight(GtkWidget *label, int chr);
  118. #if !GTK_CHECK_VERSION(2,0,0)
  119. static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
  120. gpointer data);
  121. static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
  122. gpointer data);
  123. static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
  124. gpointer data);
  125. static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
  126. gpointer data);
  127. #endif
  128. #if !GTK_CHECK_VERSION(2,4,0)
  129. static void menuitem_activate(GtkMenuItem *item, gpointer data);
  130. #endif
  131. #if GTK_CHECK_VERSION(3,0,0)
  132. static void colourchoose_response(GtkDialog *dialog,
  133. gint response_id, gpointer data);
  134. #else
  135. static void coloursel_ok(GtkButton *button, gpointer data);
  136. static void coloursel_cancel(GtkButton *button, gpointer data);
  137. #endif
  138. static void dlgparam_destroy(GtkWidget *widget, gpointer data);
  139. static int get_listitemheight(GtkWidget *widget);
  140. static int uctrl_cmp_byctrl(void *av, void *bv)
  141. {
  142. struct uctrl *a = (struct uctrl *)av;
  143. struct uctrl *b = (struct uctrl *)bv;
  144. if (a->ctrl < b->ctrl)
  145. return -1;
  146. else if (a->ctrl > b->ctrl)
  147. return +1;
  148. return 0;
  149. }
  150. static int uctrl_cmp_byctrl_find(void *av, void *bv)
  151. {
  152. dlgcontrol *a = (dlgcontrol *)av;
  153. struct uctrl *b = (struct uctrl *)bv;
  154. if (a < b->ctrl)
  155. return -1;
  156. else if (a > b->ctrl)
  157. return +1;
  158. return 0;
  159. }
  160. static int uctrl_cmp_bywidget(void *av, void *bv)
  161. {
  162. struct uctrl *a = (struct uctrl *)av;
  163. struct uctrl *b = (struct uctrl *)bv;
  164. if (a->toplevel < b->toplevel)
  165. return -1;
  166. else if (a->toplevel > b->toplevel)
  167. return +1;
  168. return 0;
  169. }
  170. static int uctrl_cmp_bywidget_find(void *av, void *bv)
  171. {
  172. GtkWidget *a = (GtkWidget *)av;
  173. struct uctrl *b = (struct uctrl *)bv;
  174. if (a < b->toplevel)
  175. return -1;
  176. else if (a > b->toplevel)
  177. return +1;
  178. return 0;
  179. }
  180. static void dlg_init(struct dlgparam *dp)
  181. {
  182. dp->byctrl = newtree234(uctrl_cmp_byctrl);
  183. dp->bywidget = newtree234(uctrl_cmp_bywidget);
  184. dp->coloursel_result.ok = false;
  185. dp->window = dp->cancelbutton = NULL;
  186. #if !GTK_CHECK_VERSION(2,0,0)
  187. dp->treeitems = NULL;
  188. dp->currtreeitem = NULL;
  189. #endif
  190. dp->curr_panel = NULL;
  191. dp->flags = 0;
  192. dp->currfocus = NULL;
  193. }
  194. static void dlg_cleanup(struct dlgparam *dp)
  195. {
  196. struct uctrl *uc;
  197. freetree234(dp->byctrl); /* doesn't free the uctrls inside */
  198. dp->byctrl = NULL;
  199. while ( (uc = index234(dp->bywidget, 0)) != NULL) {
  200. del234(dp->bywidget, uc);
  201. sfree(uc->buttons);
  202. sfree(uc);
  203. }
  204. freetree234(dp->bywidget);
  205. dp->bywidget = NULL;
  206. #if !GTK_CHECK_VERSION(2,0,0)
  207. sfree(dp->treeitems);
  208. #endif
  209. }
  210. static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
  211. {
  212. add234(dp->byctrl, uc);
  213. add234(dp->bywidget, uc);
  214. }
  215. static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, dlgcontrol *ctrl)
  216. {
  217. if (!dp->byctrl)
  218. return NULL;
  219. return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
  220. }
  221. static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
  222. {
  223. struct uctrl *ret = NULL;
  224. if (!dp->bywidget)
  225. return NULL;
  226. do {
  227. ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
  228. if (ret)
  229. return ret;
  230. w = gtk_widget_get_parent(w);
  231. } while (w);
  232. return ret;
  233. }
  234. dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp)
  235. {
  236. if (dp->currfocus != ctrl)
  237. return dp->currfocus;
  238. else
  239. return dp->lastfocus;
  240. }
  241. void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which)
  242. {
  243. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  244. assert(uc->ctrl->type == CTRL_RADIO);
  245. assert(uc->buttons != NULL);
  246. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true);
  247. }
  248. int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp)
  249. {
  250. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  251. int i;
  252. assert(uc->ctrl->type == CTRL_RADIO);
  253. assert(uc->buttons != NULL);
  254. for (i = 0; i < uc->nbuttons; i++)
  255. if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
  256. return i;
  257. return 0; /* got to return something */
  258. }
  259. void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked)
  260. {
  261. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  262. assert(uc->ctrl->type == CTRL_CHECKBOX);
  263. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
  264. }
  265. bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp)
  266. {
  267. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  268. assert(uc->ctrl->type == CTRL_CHECKBOX);
  269. return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
  270. }
  271. void dlg_editbox_set_utf8(dlgcontrol *ctrl, dlgparam *dp, char const *text)
  272. {
  273. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  274. GtkWidget *entry;
  275. char *tmpstring;
  276. assert(uc->ctrl->type == CTRL_EDITBOX);
  277. #if GTK_CHECK_VERSION(2,4,0)
  278. if (uc->combo)
  279. entry = gtk_bin_get_child(GTK_BIN(uc->combo));
  280. else
  281. #endif
  282. entry = uc->entry;
  283. assert(entry != NULL);
  284. /*
  285. * GTK 2 implements gtk_entry_set_text by means of two separate
  286. * operations: first delete the previous text leaving the empty
  287. * string, then insert the new text. This causes two calls to
  288. * the "changed" signal.
  289. *
  290. * The first call to "changed", if allowed to proceed normally,
  291. * will cause an EVENT_VALCHANGE event on the edit box, causing
  292. * a call to dlg_editbox_get() which will read the empty string
  293. * out of the GtkEntry - and promptly write it straight into the
  294. * Conf structure, which is precisely where our `text' pointer
  295. * is probably pointing, so the second editing operation will
  296. * insert that instead of the string we originally asked for.
  297. *
  298. * Hence, we must take our own copy of the text before we do
  299. * this.
  300. */
  301. tmpstring = dupstr(text);
  302. gtk_entry_set_text(GTK_ENTRY(entry), tmpstring);
  303. sfree(tmpstring);
  304. }
  305. void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
  306. {
  307. /* GTK specifies that its edit boxes are always in UTF-8 anyway,
  308. * so legacy behaviour is to use those strings unmodified */
  309. dlg_editbox_set_utf8(ctrl, dp, text);
  310. }
  311. char *dlg_editbox_get_utf8(dlgcontrol *ctrl, dlgparam *dp)
  312. {
  313. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  314. assert(uc->ctrl->type == CTRL_EDITBOX);
  315. #if GTK_CHECK_VERSION(2,4,0)
  316. if (uc->combo) {
  317. return dupstr(gtk_entry_get_text(
  318. GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
  319. }
  320. #endif
  321. if (uc->entry) {
  322. return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
  323. }
  324. unreachable("bad control type in editbox_get");
  325. }
  326. char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp)
  327. {
  328. /* GTK specifies that its edit boxes are always in UTF-8 anyway,
  329. * so legacy behaviour is to use those strings unmodified */
  330. return dlg_editbox_get_utf8(ctrl, dp);
  331. }
  332. void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp,
  333. size_t start, size_t len)
  334. {
  335. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  336. assert(uc->ctrl->type == CTRL_EDITBOX);
  337. GtkWidget *entry = NULL;
  338. #if GTK_CHECK_VERSION(2,4,0)
  339. if (uc->combo)
  340. entry = gtk_bin_get_child(GTK_BIN(uc->combo));
  341. #endif
  342. if (uc->entry)
  343. entry = uc->entry;
  344. assert(entry && "we should have a GtkEntry one way or another");
  345. gtk_editable_select_region(GTK_EDITABLE(entry), start, start + len);
  346. }
  347. #if !GTK_CHECK_VERSION(2,4,0)
  348. static void container_remove_and_destroy(GtkWidget *w, gpointer data)
  349. {
  350. GtkContainer *cont = GTK_CONTAINER(data);
  351. /* gtk_container_remove will unref the widget for us; we need not. */
  352. gtk_container_remove(cont, w);
  353. }
  354. #endif
  355. /* The `listbox' functions can also apply to combo boxes. */
  356. void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp)
  357. {
  358. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  359. assert(uc->ctrl->type == CTRL_EDITBOX ||
  360. uc->ctrl->type == CTRL_LISTBOX);
  361. #if !GTK_CHECK_VERSION(2,4,0)
  362. if (uc->menu) {
  363. gtk_container_foreach(GTK_CONTAINER(uc->menu),
  364. container_remove_and_destroy,
  365. GTK_CONTAINER(uc->menu));
  366. return;
  367. }
  368. if (uc->list) {
  369. gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
  370. return;
  371. }
  372. #endif
  373. #if GTK_CHECK_VERSION(2,0,0)
  374. if (uc->listmodel) {
  375. gtk_list_store_clear(uc->listmodel);
  376. return;
  377. }
  378. #endif
  379. unreachable("bad control type in listbox_clear");
  380. }
  381. void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index)
  382. {
  383. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  384. assert(uc->ctrl->type == CTRL_EDITBOX ||
  385. uc->ctrl->type == CTRL_LISTBOX);
  386. #if !GTK_CHECK_VERSION(2,4,0)
  387. if (uc->menu) {
  388. gtk_container_remove(
  389. GTK_CONTAINER(uc->menu),
  390. g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
  391. return;
  392. }
  393. if (uc->list) {
  394. gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
  395. return;
  396. }
  397. #endif
  398. #if GTK_CHECK_VERSION(2,0,0)
  399. if (uc->listmodel) {
  400. GtkTreePath *path;
  401. GtkTreeIter iter;
  402. assert(uc->listmodel != NULL);
  403. path = gtk_tree_path_new_from_indices(index, -1);
  404. gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
  405. gtk_list_store_remove(uc->listmodel, &iter);
  406. gtk_tree_path_free(path);
  407. return;
  408. }
  409. #endif
  410. unreachable("bad control type in listbox_del");
  411. }
  412. void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text)
  413. {
  414. dlg_listbox_addwithid(ctrl, dp, text, 0);
  415. }
  416. /*
  417. * Each listbox entry may have a numeric id associated with it.
  418. * Note that some front ends only permit a string to be stored at
  419. * each position, which means that _if_ you put two identical
  420. * strings in any listbox then you MUST not assign them different
  421. * IDs and expect to get meaningful results back.
  422. */
  423. void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
  424. char const *text, int id)
  425. {
  426. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  427. assert(uc->ctrl->type == CTRL_EDITBOX ||
  428. uc->ctrl->type == CTRL_LISTBOX);
  429. /*
  430. * This routine is long and complicated in both GTK 1 and 2,
  431. * and completely different. Sigh.
  432. */
  433. dp->flags |= FLAG_UPDATING_COMBO_LIST;
  434. #if !GTK_CHECK_VERSION(2,4,0)
  435. if (uc->menu) {
  436. /*
  437. * List item in a drop-down (but non-combo) list. Tabs are
  438. * ignored; we just provide a standard menu item with the
  439. * text.
  440. */
  441. GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
  442. gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
  443. gtk_widget_show(menuitem);
  444. g_object_set_data(G_OBJECT(menuitem), "user-data",
  445. GINT_TO_POINTER(id));
  446. g_signal_connect(G_OBJECT(menuitem), "activate",
  447. G_CALLBACK(menuitem_activate), dp);
  448. goto done;
  449. }
  450. if (uc->list && uc->entry) {
  451. /*
  452. * List item in a combo-box list, which means the sensible
  453. * thing to do is make it a perfectly normal label. Hence
  454. * tabs are disregarded.
  455. */
  456. GtkWidget *listitem = gtk_list_item_new_with_label(text);
  457. gtk_container_add(GTK_CONTAINER(uc->list), listitem);
  458. gtk_widget_show(listitem);
  459. g_object_set_data(G_OBJECT(listitem), "user-data",
  460. GINT_TO_POINTER(id));
  461. goto done;
  462. }
  463. #endif
  464. #if !GTK_CHECK_VERSION(2,0,0)
  465. if (uc->list) {
  466. /*
  467. * List item in a non-combo-box list box. We make all of
  468. * these Columns containing GtkLabels. This allows us to do
  469. * the nasty force_left hack irrespective of whether there
  470. * are tabs in the thing.
  471. */
  472. GtkWidget *listitem = gtk_list_item_new();
  473. GtkWidget *cols = columns_new(10);
  474. gint *percents;
  475. int i, ncols;
  476. /* Count the tabs in the text, and hence determine # of columns. */
  477. ncols = 1;
  478. for (i = 0; text[i]; i++)
  479. if (text[i] == '\t')
  480. ncols++;
  481. assert(ncols <=
  482. (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
  483. percents = snewn(ncols, gint);
  484. percents[ncols-1] = 100;
  485. for (i = 0; i < ncols-1; i++) {
  486. percents[i] = uc->ctrl->listbox.percentages[i];
  487. percents[ncols-1] -= percents[i];
  488. }
  489. columns_set_cols(COLUMNS(cols), ncols, percents);
  490. sfree(percents);
  491. for (i = 0; i < ncols; i++) {
  492. int len = strcspn(text, "\t");
  493. char *dup = dupprintf("%.*s", len, text);
  494. GtkWidget *label;
  495. text += len;
  496. if (*text) text++;
  497. label = gtk_label_new(dup);
  498. sfree(dup);
  499. columns_add(COLUMNS(cols), label, i, 1);
  500. columns_force_left_align(COLUMNS(cols), label);
  501. gtk_widget_show(label);
  502. }
  503. gtk_container_add(GTK_CONTAINER(listitem), cols);
  504. gtk_widget_show(cols);
  505. gtk_container_add(GTK_CONTAINER(uc->list), listitem);
  506. gtk_widget_show(listitem);
  507. if (ctrl->listbox.multisel) {
  508. g_signal_connect(G_OBJECT(listitem), "key_press_event",
  509. G_CALLBACK(listitem_multi_key), uc->adj);
  510. } else {
  511. g_signal_connect(G_OBJECT(listitem), "key_press_event",
  512. G_CALLBACK(listitem_single_key), uc->adj);
  513. }
  514. g_signal_connect(G_OBJECT(listitem), "focus_in_event",
  515. G_CALLBACK(widget_focus), dp);
  516. g_signal_connect(G_OBJECT(listitem), "button_press_event",
  517. G_CALLBACK(listitem_button_press), dp);
  518. g_signal_connect(G_OBJECT(listitem), "button_release_event",
  519. G_CALLBACK(listitem_button_release), dp);
  520. g_object_set_data(G_OBJECT(listitem), "user-data",
  521. GINT_TO_POINTER(id));
  522. goto done;
  523. }
  524. #else
  525. if (uc->listmodel) {
  526. GtkTreeIter iter;
  527. int i, cols;
  528. dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
  529. gtk_list_store_append(uc->listmodel, &iter);
  530. dp->flags &= ~FLAG_UPDATING_LISTBOX;
  531. gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
  532. /*
  533. * Now go through text and divide it into columns at the tabs,
  534. * as necessary.
  535. */
  536. cols = (uc->ctrl->type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
  537. cols = cols ? cols : 1;
  538. for (i = 0; i < cols; i++) {
  539. int collen = strcspn(text, "\t");
  540. char *tmpstr = mkstr(make_ptrlen(text, collen));
  541. gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
  542. sfree(tmpstr);
  543. text += collen;
  544. if (*text) text++;
  545. }
  546. goto done;
  547. }
  548. #endif
  549. unreachable("bad control type in listbox_addwithid");
  550. done:
  551. dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
  552. }
  553. int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index)
  554. {
  555. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  556. assert(uc->ctrl->type == CTRL_EDITBOX ||
  557. uc->ctrl->type == CTRL_LISTBOX);
  558. #if !GTK_CHECK_VERSION(2,4,0)
  559. if (uc->menu || uc->list) {
  560. GList *children;
  561. GObject *item;
  562. children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
  563. uc->list));
  564. item = G_OBJECT(g_list_nth_data(children, index));
  565. g_list_free(children);
  566. return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
  567. }
  568. #endif
  569. #if GTK_CHECK_VERSION(2,0,0)
  570. if (uc->listmodel) {
  571. GtkTreePath *path;
  572. GtkTreeIter iter;
  573. int ret;
  574. path = gtk_tree_path_new_from_indices(index, -1);
  575. gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
  576. gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
  577. gtk_tree_path_free(path);
  578. return ret;
  579. }
  580. #endif
  581. unreachable("bad control type in listbox_getid");
  582. return -1; /* placate dataflow analysis */
  583. }
  584. /* dlg_listbox_index returns <0 if no single element is selected. */
  585. int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp)
  586. {
  587. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  588. assert(uc->ctrl->type == CTRL_EDITBOX ||
  589. uc->ctrl->type == CTRL_LISTBOX);
  590. #if !GTK_CHECK_VERSION(2,4,0)
  591. if (uc->menu || uc->list) {
  592. GList *children;
  593. GtkWidget *item, *activeitem;
  594. int i;
  595. int selected = -1;
  596. if (uc->menu)
  597. activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
  598. else
  599. activeitem = NULL; /* unnecessarily placate gcc */
  600. children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
  601. uc->list));
  602. for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
  603. i++, children = children->next) {
  604. if (uc->menu ? activeitem == item :
  605. GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
  606. if (selected == -1)
  607. selected = i;
  608. else
  609. selected = -2;
  610. }
  611. }
  612. g_list_free(children);
  613. return selected < 0 ? -1 : selected;
  614. }
  615. #else
  616. if (uc->combo) {
  617. /*
  618. * This API function already does the right thing in the
  619. * case of no current selection.
  620. */
  621. return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
  622. }
  623. #endif
  624. #if GTK_CHECK_VERSION(2,0,0)
  625. if (uc->treeview) {
  626. GtkTreeSelection *treesel;
  627. GtkTreePath *path;
  628. GtkTreeModel *model;
  629. GList *sellist;
  630. gint *indices;
  631. int ret;
  632. assert(uc->treeview != NULL);
  633. treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
  634. if (gtk_tree_selection_count_selected_rows(treesel) != 1)
  635. return -1;
  636. sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
  637. assert(sellist && sellist->data);
  638. path = sellist->data;
  639. if (gtk_tree_path_get_depth(path) != 1) {
  640. ret = -1;
  641. } else {
  642. indices = gtk_tree_path_get_indices(path);
  643. if (!indices) {
  644. ret = -1;
  645. } else {
  646. ret = indices[0];
  647. }
  648. }
  649. g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
  650. g_list_free(sellist);
  651. return ret;
  652. }
  653. #endif
  654. unreachable("bad control type in listbox_index");
  655. return -1; /* placate dataflow analysis */
  656. }
  657. bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index)
  658. {
  659. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  660. assert(uc->ctrl->type == CTRL_EDITBOX ||
  661. uc->ctrl->type == CTRL_LISTBOX);
  662. #if !GTK_CHECK_VERSION(2,4,0)
  663. if (uc->menu || uc->list) {
  664. GList *children;
  665. GtkWidget *item, *activeitem;
  666. assert(uc->ctrl->type == CTRL_EDITBOX ||
  667. uc->ctrl->type == CTRL_LISTBOX);
  668. assert(uc->menu != NULL || uc->list != NULL);
  669. children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
  670. uc->list));
  671. item = GTK_WIDGET(g_list_nth_data(children, index));
  672. g_list_free(children);
  673. if (uc->menu) {
  674. activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
  675. return item == activeitem;
  676. } else {
  677. return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
  678. }
  679. }
  680. #else
  681. if (uc->combo) {
  682. /*
  683. * This API function already does the right thing in the
  684. * case of no current selection.
  685. */
  686. return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
  687. }
  688. #endif
  689. #if GTK_CHECK_VERSION(2,0,0)
  690. if (uc->treeview) {
  691. GtkTreeSelection *treesel;
  692. GtkTreePath *path;
  693. bool ret;
  694. assert(uc->treeview != NULL);
  695. treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
  696. path = gtk_tree_path_new_from_indices(index, -1);
  697. ret = gtk_tree_selection_path_is_selected(treesel, path);
  698. gtk_tree_path_free(path);
  699. return ret;
  700. }
  701. #endif
  702. unreachable("bad control type in listbox_issel");
  703. return false; /* placate dataflow analysis */
  704. }
  705. void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index)
  706. {
  707. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  708. assert(uc->ctrl->type == CTRL_EDITBOX ||
  709. uc->ctrl->type == CTRL_LISTBOX);
  710. #if !GTK_CHECK_VERSION(2,4,0)
  711. if (uc->optmenu) {
  712. gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
  713. return;
  714. }
  715. if (uc->list) {
  716. int nitems;
  717. GList *items;
  718. gdouble newtop, newbot;
  719. gtk_list_select_item(GTK_LIST(uc->list), index);
  720. /*
  721. * Scroll the list box if necessary to ensure the newly
  722. * selected item is visible.
  723. */
  724. items = gtk_container_children(GTK_CONTAINER(uc->list));
  725. nitems = g_list_length(items);
  726. if (nitems > 0) {
  727. bool modified = false;
  728. g_list_free(items);
  729. newtop = uc->adj->lower +
  730. (uc->adj->upper - uc->adj->lower) * index / nitems;
  731. newbot = uc->adj->lower +
  732. (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
  733. if (uc->adj->value > newtop) {
  734. modified = true;
  735. uc->adj->value = newtop;
  736. } else if (uc->adj->value < newbot - uc->adj->page_size) {
  737. modified = true;
  738. uc->adj->value = newbot - uc->adj->page_size;
  739. }
  740. if (modified)
  741. gtk_adjustment_value_changed(uc->adj);
  742. }
  743. return;
  744. }
  745. #else
  746. if (uc->combo) {
  747. gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
  748. return;
  749. }
  750. #endif
  751. #if GTK_CHECK_VERSION(2,0,0)
  752. if (uc->treeview) {
  753. GtkTreeSelection *treesel;
  754. GtkTreePath *path;
  755. treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
  756. path = gtk_tree_path_new_from_indices(index, -1);
  757. gtk_tree_selection_select_path(treesel, path);
  758. gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
  759. path, NULL, false, 0.0, 0.0);
  760. gtk_tree_path_free(path);
  761. return;
  762. }
  763. #endif
  764. unreachable("bad control type in listbox_select");
  765. }
  766. void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
  767. {
  768. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  769. assert(uc->ctrl->type == CTRL_TEXT);
  770. assert(uc->text != NULL);
  771. gtk_label_set_text(GTK_LABEL(uc->text), text);
  772. }
  773. void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text)
  774. {
  775. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  776. switch (uc->ctrl->type) {
  777. case CTRL_BUTTON:
  778. gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
  779. shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
  780. break;
  781. case CTRL_CHECKBOX:
  782. gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
  783. shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
  784. break;
  785. case CTRL_RADIO:
  786. gtk_label_set_text(GTK_LABEL(uc->label), text);
  787. shortcut_highlight(uc->label, ctrl->radio.shortcut);
  788. break;
  789. case CTRL_EDITBOX:
  790. gtk_label_set_text(GTK_LABEL(uc->label), text);
  791. shortcut_highlight(uc->label, ctrl->editbox.shortcut);
  792. break;
  793. case CTRL_FILESELECT:
  794. if (uc->label) {
  795. gtk_label_set_text(GTK_LABEL(uc->label), text);
  796. shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
  797. }
  798. break;
  799. case CTRL_FONTSELECT:
  800. gtk_label_set_text(GTK_LABEL(uc->label), text);
  801. shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
  802. break;
  803. case CTRL_LISTBOX:
  804. gtk_label_set_text(GTK_LABEL(uc->label), text);
  805. shortcut_highlight(uc->label, ctrl->listbox.shortcut);
  806. break;
  807. default:
  808. unreachable("bad control type in label_change");
  809. }
  810. }
  811. void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn)
  812. {
  813. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  814. /* We must copy fn->path before passing it to gtk_entry_set_text.
  815. * See comment in dlg_editbox_set() for the reasons. */
  816. char *duppath = dupstr(fn->path);
  817. assert(uc->ctrl->type == CTRL_FILESELECT);
  818. assert(uc->entry != NULL);
  819. gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath);
  820. sfree(duppath);
  821. }
  822. Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp)
  823. {
  824. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  825. assert(uc->ctrl->type == CTRL_FILESELECT);
  826. if (!uc->entry) {
  827. assert(uc->textvalue);
  828. return filename_from_str(uc->textvalue);
  829. } else {
  830. return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
  831. }
  832. }
  833. void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs)
  834. {
  835. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  836. /* We must copy fs->name before passing it to gtk_entry_set_text.
  837. * See comment in dlg_editbox_set() for the reasons. */
  838. char *dupname = dupstr(fs->name);
  839. assert(uc->ctrl->type == CTRL_FONTSELECT);
  840. assert(uc->entry != NULL);
  841. gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname);
  842. sfree(dupname);
  843. }
  844. FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp)
  845. {
  846. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  847. assert(uc->ctrl->type == CTRL_FONTSELECT);
  848. assert(uc->entry != NULL);
  849. return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
  850. }
  851. /*
  852. * Bracketing a large set of updates in these two functions will
  853. * cause the front end (if possible) to delay updating the screen
  854. * until it's all complete, thus avoiding flicker.
  855. */
  856. void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp)
  857. {
  858. /*
  859. * Apparently we can't do this at all in GTK. GtkCList supports
  860. * freeze and thaw, but not GtkList. Bah.
  861. */
  862. }
  863. void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp)
  864. {
  865. /*
  866. * Apparently we can't do this at all in GTK. GtkCList supports
  867. * freeze and thaw, but not GtkList. Bah.
  868. */
  869. }
  870. void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp)
  871. {
  872. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  873. switch (ctrl->type) {
  874. case CTRL_CHECKBOX:
  875. case CTRL_BUTTON:
  876. /* Check boxes and buttons get the focus _and_ get toggled. */
  877. gtk_widget_grab_focus(uc->toplevel);
  878. break;
  879. case CTRL_FILESELECT:
  880. case CTRL_FONTSELECT:
  881. case CTRL_EDITBOX:
  882. if (uc->entry) {
  883. /* Anything containing an edit box gets that focused. */
  884. gtk_widget_grab_focus(uc->entry);
  885. }
  886. #if GTK_CHECK_VERSION(2,4,0)
  887. else if (uc->combo) {
  888. /* Failing that, there'll be a combo box. */
  889. gtk_widget_grab_focus(uc->combo);
  890. }
  891. #endif
  892. break;
  893. case CTRL_RADIO:
  894. /*
  895. * Radio buttons: we find the currently selected button and
  896. * focus it.
  897. */
  898. for (int i = 0; i < ctrl->radio.nbuttons; i++)
  899. if (gtk_toggle_button_get_active(
  900. GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
  901. gtk_widget_grab_focus(uc->buttons[i]);
  902. }
  903. break;
  904. case CTRL_LISTBOX:
  905. #if !GTK_CHECK_VERSION(2,4,0)
  906. if (uc->optmenu) {
  907. gtk_widget_grab_focus(uc->optmenu);
  908. break;
  909. }
  910. #else
  911. if (uc->combo) {
  912. gtk_widget_grab_focus(uc->combo);
  913. break;
  914. }
  915. #endif
  916. #if !GTK_CHECK_VERSION(2,0,0)
  917. if (uc->list) {
  918. /*
  919. * For GTK-1 style list boxes, we tell it to focus one
  920. * of its children, which appears to do the Right
  921. * Thing.
  922. */
  923. gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
  924. break;
  925. }
  926. #else
  927. if (uc->treeview) {
  928. gtk_widget_grab_focus(uc->treeview);
  929. break;
  930. }
  931. #endif
  932. unreachable("bad control type in set_focus");
  933. }
  934. }
  935. /*
  936. * During event processing, you might well want to give an error
  937. * indication to the user. dlg_beep() is a quick and easy generic
  938. * error; dlg_error() puts up a message-box or equivalent.
  939. */
  940. void dlg_beep(dlgparam *dp)
  941. {
  942. gdk_display_beep(gdk_display_get_default());
  943. }
  944. static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
  945. {
  946. #if !GTK_CHECK_VERSION(2,0,0)
  947. gint x, y, w, h, dx, dy;
  948. GtkRequisition req;
  949. gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
  950. gtk_widget_size_request(GTK_WIDGET(child), &req);
  951. gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
  952. gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
  953. /*
  954. * One corner of the transient will be offset inwards, by 1/4
  955. * of the parent window's size, from the corresponding corner
  956. * of the parent window. The corner will be chosen so as to
  957. * place the transient closer to the centre of the screen; this
  958. * should avoid transients going off the edge of the screen on
  959. * a regular basis.
  960. */
  961. if (x + w/2 < gdk_screen_width() / 2)
  962. dx = x + w/4; /* work from left edges */
  963. else
  964. dx = x + 3*w/4 - req.width; /* work from right edges */
  965. if (y + h/2 < gdk_screen_height() / 2)
  966. dy = y + h/4; /* work from top edges */
  967. else
  968. dy = y + 3*h/4 - req.height; /* work from bottom edges */
  969. gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
  970. #endif
  971. }
  972. void trivial_post_dialog_fn(void *vctx, int result)
  973. {
  974. }
  975. void dlg_error_msg(dlgparam *dp, const char *msg)
  976. {
  977. create_message_box(
  978. dp->window, "Error", msg,
  979. string_width("Some sort of text about a config-box error message"),
  980. false, &buttons_ok, trivial_post_dialog_fn, NULL);
  981. }
  982. /*
  983. * This function signals to the front end that the dialog's
  984. * processing is completed, and passes an integer value (typically
  985. * a success status).
  986. */
  987. void dlg_end(dlgparam *dp, int value)
  988. {
  989. dp->retval = value;
  990. gtk_widget_destroy(dp->window);
  991. }
  992. void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp)
  993. {
  994. struct uctrl *uc;
  995. if (ctrl) {
  996. if (ctrl->handler != NULL)
  997. ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH);
  998. } else {
  999. int i;
  1000. for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
  1001. assert(uc->ctrl != NULL);
  1002. if (uc->ctrl->handler != NULL)
  1003. uc->ctrl->handler(uc->ctrl, dp,
  1004. dp->data, EVENT_REFRESH);
  1005. }
  1006. }
  1007. }
  1008. void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b)
  1009. {
  1010. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  1011. #if GTK_CHECK_VERSION(3,0,0)
  1012. GtkWidget *coloursel =
  1013. gtk_color_chooser_dialog_new("Select a colour",
  1014. GTK_WINDOW(dp->window));
  1015. gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false);
  1016. #else
  1017. GtkWidget *okbutton, *cancelbutton;
  1018. GtkWidget *coloursel =
  1019. gtk_color_selection_dialog_new("Select a colour");
  1020. GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
  1021. GtkColorSelection *cs = GTK_COLOR_SELECTION(
  1022. gtk_color_selection_dialog_get_color_selection(ccs));
  1023. gtk_color_selection_set_has_opacity_control(cs, false);
  1024. #endif
  1025. dp->coloursel_result.ok = false;
  1026. gtk_window_set_modal(GTK_WINDOW(coloursel), true);
  1027. #if GTK_CHECK_VERSION(3,0,0)
  1028. {
  1029. GdkRGBA rgba;
  1030. rgba.red = r / 255.0;
  1031. rgba.green = g / 255.0;
  1032. rgba.blue = b / 255.0;
  1033. rgba.alpha = 1.0; /* fully opaque! */
  1034. gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
  1035. }
  1036. #elif GTK_CHECK_VERSION(2,0,0)
  1037. {
  1038. GdkColor col;
  1039. col.red = r * 0x0101;
  1040. col.green = g * 0x0101;
  1041. col.blue = b * 0x0101;
  1042. gtk_color_selection_set_current_color(cs, &col);
  1043. }
  1044. #else
  1045. {
  1046. gdouble cvals[4];
  1047. cvals[0] = r / 255.0;
  1048. cvals[1] = g / 255.0;
  1049. cvals[2] = b / 255.0;
  1050. cvals[3] = 1.0; /* fully opaque! */
  1051. gtk_color_selection_set_color(cs, cvals);
  1052. }
  1053. #endif
  1054. g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
  1055. #if GTK_CHECK_VERSION(3,0,0)
  1056. g_signal_connect(G_OBJECT(coloursel), "response",
  1057. G_CALLBACK(colourchoose_response), (gpointer)dp);
  1058. #else
  1059. #if GTK_CHECK_VERSION(2,0,0)
  1060. g_object_get(G_OBJECT(ccs),
  1061. "ok-button", &okbutton,
  1062. "cancel-button", &cancelbutton,
  1063. (const char *)NULL);
  1064. #else
  1065. okbutton = ccs->ok_button;
  1066. cancelbutton = ccs->cancel_button;
  1067. #endif
  1068. g_object_set_data(G_OBJECT(okbutton), "user-data",
  1069. (gpointer)coloursel);
  1070. g_object_set_data(G_OBJECT(cancelbutton), "user-data",
  1071. (gpointer)coloursel);
  1072. g_signal_connect(G_OBJECT(okbutton), "clicked",
  1073. G_CALLBACK(coloursel_ok), (gpointer)dp);
  1074. g_signal_connect(G_OBJECT(cancelbutton), "clicked",
  1075. G_CALLBACK(coloursel_cancel), (gpointer)dp);
  1076. g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
  1077. G_CALLBACK(gtk_widget_destroy),
  1078. (gpointer)coloursel);
  1079. g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
  1080. G_CALLBACK(gtk_widget_destroy),
  1081. (gpointer)coloursel);
  1082. #endif
  1083. gtk_widget_show(coloursel);
  1084. }
  1085. bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
  1086. int *r, int *g, int *b)
  1087. {
  1088. if (dp->coloursel_result.ok) {
  1089. *r = dp->coloursel_result.r;
  1090. *g = dp->coloursel_result.g;
  1091. *b = dp->coloursel_result.b;
  1092. return true;
  1093. } else
  1094. return false;
  1095. }
  1096. /* ----------------------------------------------------------------------
  1097. * Signal handlers while the dialog box is active.
  1098. */
  1099. static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
  1100. gpointer data)
  1101. {
  1102. struct dlgparam *dp = (struct dlgparam *)data;
  1103. struct uctrl *uc = dlg_find_bywidget(dp, widget);
  1104. dlgcontrol *focus;
  1105. if (uc && uc->ctrl)
  1106. focus = uc->ctrl;
  1107. else
  1108. focus = NULL;
  1109. if (focus != dp->currfocus) {
  1110. dp->lastfocus = dp->currfocus;
  1111. dp->currfocus = focus;
  1112. }
  1113. return false;
  1114. }
  1115. static void button_clicked(GtkButton *button, gpointer data)
  1116. {
  1117. struct dlgparam *dp = (struct dlgparam *)data;
  1118. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
  1119. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
  1120. }
  1121. static void button_toggled(GtkToggleButton *tb, gpointer data)
  1122. {
  1123. struct dlgparam *dp = (struct dlgparam *)data;
  1124. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
  1125. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
  1126. }
  1127. static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
  1128. gpointer data)
  1129. {
  1130. /*
  1131. * GtkEntry has a nasty habit of eating the Return key, which
  1132. * is unhelpful since it doesn't actually _do_ anything with it
  1133. * (it calls gtk_widget_activate, but our edit boxes never need
  1134. * activating). So I catch Return before GtkEntry sees it, and
  1135. * pass it straight on to the parent widget. Effect: hitting
  1136. * Return in an edit box will now activate the default button
  1137. * in the dialog just like it will everywhere else.
  1138. */
  1139. GtkWidget *parent = gtk_widget_get_parent(widget);
  1140. if (event->keyval == GDK_KEY_Return && parent != NULL) {
  1141. gboolean return_val;
  1142. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  1143. g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
  1144. event, &return_val);
  1145. return return_val;
  1146. }
  1147. return false;
  1148. }
  1149. static void editbox_changed(GtkEditable *ed, gpointer data)
  1150. {
  1151. struct dlgparam *dp = (struct dlgparam *)data;
  1152. if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
  1153. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
  1154. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
  1155. }
  1156. }
  1157. static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
  1158. gpointer data)
  1159. {
  1160. struct dlgparam *dp = (struct dlgparam *)data;
  1161. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
  1162. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
  1163. return false;
  1164. }
  1165. #if !GTK_CHECK_VERSION(2,0,0)
  1166. /*
  1167. * GTK 1 list box event handlers.
  1168. */
  1169. static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
  1170. gpointer data, bool multiple)
  1171. {
  1172. GtkAdjustment *adj = GTK_ADJUSTMENT(data);
  1173. if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
  1174. event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
  1175. event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
  1176. event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
  1177. /*
  1178. * Up, Down, PgUp or PgDn have been pressed on a ListItem
  1179. * in a list box. So, if the list box is single-selection:
  1180. *
  1181. * - if the list item in question isn't already selected,
  1182. * we simply select it.
  1183. * - otherwise, we find the next one (or next
  1184. * however-far-away) in whichever direction we're going,
  1185. * and select that.
  1186. * + in this case, we must also fiddle with the
  1187. * scrollbar to ensure the newly selected item is
  1188. * actually visible.
  1189. *
  1190. * If it's multiple-selection, we do all of the above
  1191. * except actually selecting anything, so we move the focus
  1192. * and fiddle the scrollbar to follow it.
  1193. */
  1194. GtkWidget *list = item->parent;
  1195. g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
  1196. if (!multiple &&
  1197. GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
  1198. gtk_list_select_child(GTK_LIST(list), item);
  1199. } else {
  1200. int direction =
  1201. (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
  1202. event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
  1203. ? -1 : +1;
  1204. int step =
  1205. (event->keyval==GDK_Page_Down ||
  1206. event->keyval==GDK_KP_Page_Down ||
  1207. event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
  1208. ? 2 : 1;
  1209. int i, n;
  1210. GList *children, *chead;
  1211. chead = children = gtk_container_children(GTK_CONTAINER(list));
  1212. n = g_list_length(children);
  1213. if (step == 2) {
  1214. /*
  1215. * Figure out how many list items to a screenful,
  1216. * and adjust the step appropriately.
  1217. */
  1218. step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
  1219. step--; /* go by one less than that */
  1220. }
  1221. i = 0;
  1222. while (children != NULL) {
  1223. if (item == children->data)
  1224. break;
  1225. children = children->next;
  1226. i++;
  1227. }
  1228. while (step > 0) {
  1229. if (direction < 0 && i > 0)
  1230. children = children->prev, i--;
  1231. else if (direction > 0 && i < n-1)
  1232. children = children->next, i++;
  1233. step--;
  1234. }
  1235. if (children && children->data) {
  1236. if (!multiple)
  1237. gtk_list_select_child(GTK_LIST(list),
  1238. GTK_WIDGET(children->data));
  1239. gtk_widget_grab_focus(GTK_WIDGET(children->data));
  1240. gtk_adjustment_clamp_page(
  1241. adj,
  1242. adj->lower + (adj->upper-adj->lower) * i / n,
  1243. adj->lower + (adj->upper-adj->lower) * (i+1) / n);
  1244. }
  1245. g_list_free(chead);
  1246. }
  1247. return true;
  1248. }
  1249. return false;
  1250. }
  1251. static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
  1252. gpointer data)
  1253. {
  1254. return listitem_key(item, event, data, false);
  1255. }
  1256. static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
  1257. gpointer data)
  1258. {
  1259. return listitem_key(item, event, data, true);
  1260. }
  1261. static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
  1262. gpointer data)
  1263. {
  1264. struct dlgparam *dp = (struct dlgparam *)data;
  1265. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
  1266. switch (event->type) {
  1267. default:
  1268. case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
  1269. case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
  1270. case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
  1271. }
  1272. return false;
  1273. }
  1274. static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
  1275. gpointer data)
  1276. {
  1277. struct dlgparam *dp = (struct dlgparam *)data;
  1278. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
  1279. if (uc->nclicks>1) {
  1280. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
  1281. return true;
  1282. }
  1283. return false;
  1284. }
  1285. static void list_selchange(GtkList *list, gpointer data)
  1286. {
  1287. struct dlgparam *dp = (struct dlgparam *)data;
  1288. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
  1289. if (!uc) return;
  1290. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
  1291. }
  1292. static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
  1293. {
  1294. int index = dlg_listbox_index(uc->ctrl, dp);
  1295. GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
  1296. GtkWidget *child;
  1297. if ((index < 0) ||
  1298. (index == 0 && direction < 0) ||
  1299. (index == g_list_length(children)-1 && direction > 0)) {
  1300. gdk_display_beep(gdk_display_get_default());
  1301. return;
  1302. }
  1303. child = g_list_nth_data(children, index);
  1304. gtk_widget_ref(child);
  1305. gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
  1306. g_list_free(children);
  1307. children = NULL;
  1308. children = g_list_append(children, child);
  1309. gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
  1310. gtk_list_select_item(GTK_LIST(uc->list), index + direction);
  1311. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
  1312. }
  1313. static void draglist_up(GtkButton *button, gpointer data)
  1314. {
  1315. struct dlgparam *dp = (struct dlgparam *)data;
  1316. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
  1317. draglist_move(dp, uc, -1);
  1318. }
  1319. static void draglist_down(GtkButton *button, gpointer data)
  1320. {
  1321. struct dlgparam *dp = (struct dlgparam *)data;
  1322. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
  1323. draglist_move(dp, uc, +1);
  1324. }
  1325. #else /* !GTK_CHECK_VERSION(2,0,0) */
  1326. /*
  1327. * GTK 2 list box event handlers.
  1328. */
  1329. static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
  1330. GtkTreeViewColumn *column, gpointer data)
  1331. {
  1332. struct dlgparam *dp = (struct dlgparam *)data;
  1333. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
  1334. if (uc)
  1335. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
  1336. }
  1337. static void listbox_selchange(GtkTreeSelection *treeselection,
  1338. gpointer data)
  1339. {
  1340. struct dlgparam *dp = (struct dlgparam *)data;
  1341. GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
  1342. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
  1343. if (uc)
  1344. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
  1345. }
  1346. struct draglist_valchange_ctx {
  1347. struct uctrl *uc;
  1348. struct dlgparam *dp;
  1349. };
  1350. static gboolean draglist_valchange(gpointer data)
  1351. {
  1352. struct draglist_valchange_ctx *ctx =
  1353. (struct draglist_valchange_ctx *)data;
  1354. ctx->uc->ctrl->handler(ctx->uc->ctrl, ctx->dp,
  1355. ctx->dp->data, EVENT_VALCHANGE);
  1356. sfree(ctx);
  1357. return false;
  1358. }
  1359. static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
  1360. GtkTreeIter *iter, gpointer data)
  1361. {
  1362. struct dlgparam *dp = (struct dlgparam *)data;
  1363. gpointer tree;
  1364. struct uctrl *uc;
  1365. if (dp->flags & FLAG_UPDATING_LISTBOX)
  1366. return; /* not a user drag operation */
  1367. tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
  1368. uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
  1369. if (uc) {
  1370. /*
  1371. * We should cause EVENT_VALCHANGE on the list box, now
  1372. * that its rows have been reordered. However, the GTK 2
  1373. * docs say that at the point this signal is received the
  1374. * new row might not have actually been filled in yet.
  1375. *
  1376. * (So what smegging use is it then, eh? Don't suppose it
  1377. * occurred to you at any point that letting the
  1378. * application know _after_ the reordering was compelete
  1379. * might be helpful to someone?)
  1380. *
  1381. * To get round this, I schedule an idle function, which I
  1382. * hope won't be called until the main event loop is
  1383. * re-entered after the drag-and-drop handler has finished
  1384. * furtling with the list store.
  1385. */
  1386. struct draglist_valchange_ctx *ctx =
  1387. snew(struct draglist_valchange_ctx);
  1388. ctx->uc = uc;
  1389. ctx->dp = dp;
  1390. g_idle_add(draglist_valchange, ctx);
  1391. }
  1392. }
  1393. #endif /* !GTK_CHECK_VERSION(2,0,0) */
  1394. #if !GTK_CHECK_VERSION(2,4,0)
  1395. static void menuitem_activate(GtkMenuItem *item, gpointer data)
  1396. {
  1397. struct dlgparam *dp = (struct dlgparam *)data;
  1398. GtkWidget *menushell = GTK_WIDGET(item)->parent;
  1399. gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
  1400. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
  1401. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
  1402. }
  1403. #else
  1404. static void droplist_selchange(GtkComboBox *combo, gpointer data)
  1405. {
  1406. struct dlgparam *dp = (struct dlgparam *)data;
  1407. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
  1408. if (uc)
  1409. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
  1410. }
  1411. #endif /* !GTK_CHECK_VERSION(2,4,0) */
  1412. static void filechoose_emit_value(struct dlgparam *dp, struct uctrl *uc,
  1413. const char *name)
  1414. {
  1415. if (uc->entry) {
  1416. gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
  1417. } else {
  1418. uc->textvalue = name;
  1419. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
  1420. uc->textvalue = NULL;
  1421. }
  1422. }
  1423. #ifdef USE_GTK_FILE_CHOOSER_DIALOG
  1424. static void filechoose_response(GtkDialog *dialog, gint response,
  1425. gpointer data)
  1426. {
  1427. struct dlgparam *dp = (struct dlgparam *)data;
  1428. struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
  1429. if (response == GTK_RESPONSE_ACCEPT) {
  1430. gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
  1431. filechoose_emit_value(dp, uc, name);
  1432. g_free(name);
  1433. }
  1434. gtk_widget_destroy(GTK_WIDGET(dialog));
  1435. }
  1436. #else
  1437. static void filesel_ok(GtkButton *button, gpointer data)
  1438. {
  1439. struct dlgparam *dp = (struct dlgparam *)data;
  1440. gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
  1441. struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
  1442. const char *name = gtk_file_selection_get_filename(
  1443. GTK_FILE_SELECTION(filesel));
  1444. filechoose_emit_value(dp, uc, name);
  1445. }
  1446. #endif
  1447. static void fontsel_ok(GtkButton *button, gpointer data)
  1448. {
  1449. /* struct dlgparam *dp = (struct dlgparam *)data; */
  1450. #if !GTK_CHECK_VERSION(2,0,0)
  1451. gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
  1452. struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
  1453. const char *name = gtk_font_selection_dialog_get_font_name(
  1454. GTK_FONT_SELECTION_DIALOG(fontsel));
  1455. gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
  1456. #else
  1457. unifontsel *fontsel = (unifontsel *)g_object_get_data(
  1458. G_OBJECT(button), "user-data");
  1459. struct uctrl *uc = (struct uctrl *)fontsel->user_data;
  1460. char *name = unifontsel_get_name(fontsel);
  1461. assert(name); /* should always be ok after OK pressed */
  1462. gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
  1463. sfree(name);
  1464. #endif
  1465. }
  1466. #if GTK_CHECK_VERSION(3,0,0)
  1467. static void colourchoose_response(GtkDialog *dialog,
  1468. gint response_id, gpointer data)
  1469. {
  1470. struct dlgparam *dp = (struct dlgparam *)data;
  1471. struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
  1472. if (response_id == GTK_RESPONSE_OK) {
  1473. GdkRGBA rgba;
  1474. gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
  1475. dp->coloursel_result.r = (int) (255 * rgba.red);
  1476. dp->coloursel_result.g = (int) (255 * rgba.green);
  1477. dp->coloursel_result.b = (int) (255 * rgba.blue);
  1478. dp->coloursel_result.ok = true;
  1479. } else {
  1480. dp->coloursel_result.ok = false;
  1481. }
  1482. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
  1483. gtk_widget_destroy(GTK_WIDGET(dialog));
  1484. }
  1485. #else /* GTK 1/2 coloursel response handlers */
  1486. static void coloursel_ok(GtkButton *button, gpointer data)
  1487. {
  1488. struct dlgparam *dp = (struct dlgparam *)data;
  1489. gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
  1490. struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
  1491. #if GTK_CHECK_VERSION(2,0,0)
  1492. {
  1493. GtkColorSelection *cs = GTK_COLOR_SELECTION(
  1494. gtk_color_selection_dialog_get_color_selection(
  1495. GTK_COLOR_SELECTION_DIALOG(coloursel)));
  1496. GdkColor col;
  1497. gtk_color_selection_get_current_color(cs, &col);
  1498. dp->coloursel_result.r = col.red / 0x0100;
  1499. dp->coloursel_result.g = col.green / 0x0100;
  1500. dp->coloursel_result.b = col.blue / 0x0100;
  1501. }
  1502. #else
  1503. {
  1504. GtkColorSelection *cs = GTK_COLOR_SELECTION(
  1505. gtk_color_selection_dialog_get_color_selection(
  1506. GTK_COLOR_SELECTION_DIALOG(coloursel)));
  1507. gdouble cvals[4];
  1508. gtk_color_selection_get_color(cs, cvals);
  1509. dp->coloursel_result.r = (int) (255 * cvals[0]);
  1510. dp->coloursel_result.g = (int) (255 * cvals[1]);
  1511. dp->coloursel_result.b = (int) (255 * cvals[2]);
  1512. }
  1513. #endif
  1514. dp->coloursel_result.ok = true;
  1515. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
  1516. }
  1517. static void coloursel_cancel(GtkButton *button, gpointer data)
  1518. {
  1519. struct dlgparam *dp = (struct dlgparam *)data;
  1520. gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
  1521. struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
  1522. dp->coloursel_result.ok = false;
  1523. uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
  1524. }
  1525. #endif /* end of coloursel response handlers */
  1526. static void filefont_clicked(GtkButton *button, gpointer data)
  1527. {
  1528. struct dlgparam *dp = (struct dlgparam *)data;
  1529. struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
  1530. if (uc->ctrl->type == CTRL_FILESELECT) {
  1531. /*
  1532. * FIXME: do something about uc->ctrl->fileselect.filter
  1533. */
  1534. #ifdef USE_GTK_FILE_CHOOSER_DIALOG
  1535. GtkWidget *filechoose = gtk_file_chooser_dialog_new(
  1536. uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
  1537. (uc->ctrl->fileselect.for_writing ?
  1538. GTK_FILE_CHOOSER_ACTION_SAVE :
  1539. GTK_FILE_CHOOSER_ACTION_OPEN),
  1540. STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
  1541. STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
  1542. (const gchar *)NULL);
  1543. gtk_window_set_modal(GTK_WINDOW(filechoose), true);
  1544. g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
  1545. g_signal_connect(G_OBJECT(filechoose), "response",
  1546. G_CALLBACK(filechoose_response), (gpointer)dp);
  1547. gtk_widget_show(filechoose);
  1548. #else
  1549. GtkWidget *filesel =
  1550. gtk_file_selection_new(uc->ctrl->fileselect.title);
  1551. gtk_window_set_modal(GTK_WINDOW(filesel), true);
  1552. g_object_set_data(
  1553. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
  1554. (gpointer)filesel);
  1555. g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
  1556. g_signal_connect(
  1557. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
  1558. G_CALLBACK(filesel_ok), (gpointer)dp);
  1559. g_signal_connect_swapped(
  1560. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
  1561. G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
  1562. g_signal_connect_swapped(
  1563. G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
  1564. G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
  1565. gtk_widget_show(filesel);
  1566. #endif
  1567. }
  1568. if (uc->ctrl->type == CTRL_FONTSELECT) {
  1569. const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
  1570. #if !GTK_CHECK_VERSION(2,0,0)
  1571. /*
  1572. * Use the GTK 1 standard font selector.
  1573. */
  1574. gchar *spacings[] = { "c", "m", NULL };
  1575. GtkWidget *fontsel =
  1576. gtk_font_selection_dialog_new("Select a font");
  1577. gtk_window_set_modal(GTK_WINDOW(fontsel), true);
  1578. gtk_font_selection_dialog_set_filter(
  1579. GTK_FONT_SELECTION_DIALOG(fontsel),
  1580. GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
  1581. NULL, NULL, NULL, NULL, spacings, NULL);
  1582. if (!gtk_font_selection_dialog_set_font_name(
  1583. GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
  1584. /*
  1585. * If the font name wasn't found as it was, try opening
  1586. * it and extracting its FONT property. This should
  1587. * have the effect of mapping short aliases into true
  1588. * XLFDs.
  1589. */
  1590. GdkFont *font = gdk_font_load(fontname);
  1591. if (font) {
  1592. XFontStruct *xfs = GDK_FONT_XFONT(font);
  1593. Display *disp = get_x11_display();
  1594. Atom fontprop = XInternAtom(disp, "FONT", False);
  1595. unsigned long ret;
  1596. assert(disp); /* this is GTK1! */
  1597. gdk_font_ref(font);
  1598. if (XGetFontProperty(xfs, fontprop, &ret)) {
  1599. char *name = XGetAtomName(disp, (Atom)ret);
  1600. if (name)
  1601. gtk_font_selection_dialog_set_font_name(
  1602. GTK_FONT_SELECTION_DIALOG(fontsel), name);
  1603. }
  1604. gdk_font_unref(font);
  1605. }
  1606. }
  1607. g_object_set_data(
  1608. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1609. "user-data", (gpointer)fontsel);
  1610. g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
  1611. g_signal_connect(
  1612. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1613. "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
  1614. g_signal_connect_swapped(
  1615. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1616. "clicked", G_CALLBACK(gtk_widget_destroy),
  1617. (gpointer)fontsel);
  1618. g_signal_connect_swapped(
  1619. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
  1620. "clicked", G_CALLBACK(gtk_widget_destroy),
  1621. (gpointer)fontsel);
  1622. gtk_widget_show(fontsel);
  1623. #else /* !GTK_CHECK_VERSION(2,0,0) */
  1624. /*
  1625. * Use the unifontsel code provided in unifont.c.
  1626. */
  1627. unifontsel *fontsel = unifontsel_new("Select a font");
  1628. gtk_window_set_modal(fontsel->window, true);
  1629. unifontsel_set_name(fontsel, fontname);
  1630. g_object_set_data(G_OBJECT(fontsel->ok_button),
  1631. "user-data", (gpointer)fontsel);
  1632. fontsel->user_data = uc;
  1633. g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
  1634. G_CALLBACK(fontsel_ok), (gpointer)dp);
  1635. g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
  1636. G_CALLBACK(unifontsel_destroy),
  1637. (gpointer)fontsel);
  1638. g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
  1639. G_CALLBACK(unifontsel_destroy),
  1640. (gpointer)fontsel);
  1641. gtk_widget_show(GTK_WIDGET(fontsel->window));
  1642. #endif /* !GTK_CHECK_VERSION(2,0,0) */
  1643. }
  1644. }
  1645. #if !GTK_CHECK_VERSION(3,0,0)
  1646. static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
  1647. gpointer data)
  1648. {
  1649. struct dlgparam *dp = (struct dlgparam *)data;
  1650. struct uctrl *uc = dlg_find_bywidget(dp, widget);
  1651. gtk_widget_set_size_request(uc->text, alloc->width, -1);
  1652. gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label);
  1653. g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
  1654. }
  1655. #endif
  1656. /* ----------------------------------------------------------------------
  1657. * This function does the main layout work: it reads a controlset,
  1658. * it creates the relevant GTK controls, and returns a GtkWidget
  1659. * containing the result. (This widget might be a title of some
  1660. * sort, it might be a Columns containing many controls, or it
  1661. * might be a GtkFrame containing a Columns; whatever it is, it's
  1662. * definitely a GtkWidget and should probably be added to a
  1663. * GtkVbox.)
  1664. *
  1665. * `win' is required for setting the default button. If it is
  1666. * non-NULL, all buttons created will be default-capable (so they
  1667. * have extra space round them for the default highlight).
  1668. */
  1669. GtkWidget *layout_ctrls(
  1670. struct dlgparam *dp, struct selparam *sp, struct Shortcuts *scs,
  1671. struct controlset *s, GtkWindow *win)
  1672. {
  1673. Columns *cols;
  1674. GtkWidget *ret;
  1675. int i;
  1676. if (!s->boxname) {
  1677. /* This controlset is a panel title. */
  1678. assert(s->boxtitle);
  1679. return gtk_label_new(s->boxtitle);
  1680. }
  1681. /*
  1682. * Otherwise, we expect to be laying out actual controls, so
  1683. * we'll start by creating a Columns for the purpose.
  1684. */
  1685. cols = COLUMNS(columns_new(4));
  1686. ret = GTK_WIDGET(cols);
  1687. gtk_widget_show(ret);
  1688. /*
  1689. * Create a containing frame if we have a box name.
  1690. */
  1691. if (*s->boxname) {
  1692. ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
  1693. gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
  1694. gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
  1695. gtk_widget_show(ret);
  1696. }
  1697. /*
  1698. * Now iterate through the controls themselves, create them,
  1699. * and add them to the Columns.
  1700. */
  1701. for (i = 0; i < s->ncontrols; i++) {
  1702. dlgcontrol *ctrl = s->ctrls[i];
  1703. struct uctrl *uc;
  1704. bool left = false;
  1705. GtkWidget *w = NULL;
  1706. switch (ctrl->type) {
  1707. case CTRL_COLUMNS: {
  1708. static const int simplecols[1] = { 100 };
  1709. columns_set_cols(cols, ctrl->columns.ncols,
  1710. (ctrl->columns.percentages ?
  1711. ctrl->columns.percentages : simplecols));
  1712. continue; /* no actual control created */
  1713. }
  1714. case CTRL_TABDELAY: {
  1715. struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
  1716. if (uc)
  1717. columns_taborder_last(cols, uc->toplevel);
  1718. continue; /* no actual control created */
  1719. }
  1720. }
  1721. uc = snew(struct uctrl);
  1722. uc->sp = sp;
  1723. uc->ctrl = ctrl;
  1724. uc->buttons = NULL;
  1725. uc->entry = NULL;
  1726. #if !GTK_CHECK_VERSION(2,4,0)
  1727. uc->list = uc->menu = uc->optmenu = NULL;
  1728. #else
  1729. uc->combo = NULL;
  1730. #endif
  1731. #if GTK_CHECK_VERSION(2,0,0)
  1732. uc->treeview = NULL;
  1733. uc->listmodel = NULL;
  1734. #endif
  1735. uc->button = uc->text = NULL;
  1736. uc->label = NULL;
  1737. uc->nclicks = 0;
  1738. switch (ctrl->type) {
  1739. case CTRL_BUTTON:
  1740. w = gtk_button_new_with_label(ctrl->label);
  1741. if (win) {
  1742. gtk_widget_set_can_default(w, true);
  1743. if (ctrl->button.isdefault)
  1744. gtk_window_set_default(win, w);
  1745. if (ctrl->button.iscancel)
  1746. dp->cancelbutton = w;
  1747. }
  1748. g_signal_connect(G_OBJECT(w), "clicked",
  1749. G_CALLBACK(button_clicked), dp);
  1750. g_signal_connect(G_OBJECT(w), "focus_in_event",
  1751. G_CALLBACK(widget_focus), dp);
  1752. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1753. ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
  1754. break;
  1755. case CTRL_CHECKBOX:
  1756. w = gtk_check_button_new_with_label(ctrl->label);
  1757. g_signal_connect(G_OBJECT(w), "toggled",
  1758. G_CALLBACK(button_toggled), dp);
  1759. g_signal_connect(G_OBJECT(w), "focus_in_event",
  1760. G_CALLBACK(widget_focus), dp);
  1761. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1762. ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
  1763. left = true;
  1764. break;
  1765. case CTRL_RADIO: {
  1766. /*
  1767. * Radio buttons get to go inside their own Columns, no
  1768. * matter what.
  1769. */
  1770. gint i, *percentages;
  1771. GSList *group;
  1772. w = columns_new(0);
  1773. if (ctrl->label) {
  1774. GtkWidget *label = gtk_label_new(ctrl->label);
  1775. columns_add(COLUMNS(w), label, 0, 1);
  1776. columns_force_left_align(COLUMNS(w), label);
  1777. gtk_widget_show(label);
  1778. shortcut_add(scs, label, ctrl->radio.shortcut,
  1779. SHORTCUT_UCTRL, uc);
  1780. uc->label = label;
  1781. }
  1782. percentages = g_new(gint, ctrl->radio.ncolumns);
  1783. for (i = 0; i < ctrl->radio.ncolumns; i++) {
  1784. percentages[i] =
  1785. ((100 * (i+1) / ctrl->radio.ncolumns) -
  1786. 100 * i / ctrl->radio.ncolumns);
  1787. }
  1788. columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
  1789. percentages);
  1790. g_free(percentages);
  1791. group = NULL;
  1792. uc->nbuttons = ctrl->radio.nbuttons;
  1793. uc->buttons = snewn(uc->nbuttons, GtkWidget *);
  1794. for (i = 0; i < ctrl->radio.nbuttons; i++) {
  1795. GtkWidget *b;
  1796. gint colstart;
  1797. b = gtk_radio_button_new_with_label(
  1798. group, ctrl->radio.buttons[i]);
  1799. uc->buttons[i] = b;
  1800. group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
  1801. colstart = i % ctrl->radio.ncolumns;
  1802. columns_add(COLUMNS(w), b, colstart,
  1803. (i == ctrl->radio.nbuttons-1 ?
  1804. ctrl->radio.ncolumns - colstart : 1));
  1805. columns_force_left_align(COLUMNS(w), b);
  1806. gtk_widget_show(b);
  1807. g_signal_connect(G_OBJECT(b), "toggled",
  1808. G_CALLBACK(button_toggled), dp);
  1809. g_signal_connect(G_OBJECT(b), "focus_in_event",
  1810. G_CALLBACK(widget_focus), dp);
  1811. if (ctrl->radio.shortcuts) {
  1812. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
  1813. ctrl->radio.shortcuts[i],
  1814. SHORTCUT_UCTRL, uc);
  1815. }
  1816. }
  1817. break;
  1818. }
  1819. case CTRL_EDITBOX: {
  1820. GtkWidget *signalobject;
  1821. if (ctrl->editbox.has_list) {
  1822. #if !GTK_CHECK_VERSION(2,4,0)
  1823. /*
  1824. * GTK 1 combo box.
  1825. */
  1826. w = gtk_combo_new();
  1827. gtk_combo_set_value_in_list(GTK_COMBO(w), false, true);
  1828. uc->entry = GTK_COMBO(w)->entry;
  1829. uc->list = GTK_COMBO(w)->list;
  1830. signalobject = uc->entry;
  1831. #else
  1832. /*
  1833. * GTK 2 combo box.
  1834. */
  1835. uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
  1836. G_TYPE_STRING);
  1837. w = gtk_combo_box_new_with_model_and_entry(
  1838. GTK_TREE_MODEL(uc->listmodel));
  1839. g_object_set(G_OBJECT(w), "entry-text-column", 1,
  1840. (const char *)NULL);
  1841. /* We cannot support password combo boxes. */
  1842. assert(!ctrl->editbox.password);
  1843. uc->combo = w;
  1844. signalobject = uc->combo;
  1845. #endif
  1846. } else {
  1847. w = gtk_entry_new();
  1848. if (ctrl->editbox.password)
  1849. gtk_entry_set_visibility(GTK_ENTRY(w), false);
  1850. uc->entry = w;
  1851. signalobject = w;
  1852. }
  1853. g_signal_connect(G_OBJECT(signalobject), "changed",
  1854. G_CALLBACK(editbox_changed), dp);
  1855. g_signal_connect(G_OBJECT(signalobject), "key_press_event",
  1856. G_CALLBACK(editbox_key), dp);
  1857. g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
  1858. G_CALLBACK(widget_focus), dp);
  1859. g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
  1860. G_CALLBACK(editbox_lostfocus), dp);
  1861. g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
  1862. G_CALLBACK(editbox_lostfocus), dp);
  1863. #if !GTK_CHECK_VERSION(3,0,0)
  1864. /*
  1865. * Edit boxes, for some strange reason, have a minimum
  1866. * width of 150 in GTK 1.2. We don't want this - we'd
  1867. * rather the edit boxes acquired their natural width
  1868. * from the column layout of the rest of the box.
  1869. */
  1870. {
  1871. GtkRequisition req;
  1872. gtk_widget_size_request(w, &req);
  1873. gtk_widget_set_size_request(w, 10, req.height);
  1874. }
  1875. #else
  1876. /*
  1877. * In GTK 3, this is still true, but there's a special
  1878. * method for GtkEntry in particular to fix it.
  1879. */
  1880. if (GTK_IS_ENTRY(w))
  1881. gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
  1882. #endif
  1883. if (ctrl->label) {
  1884. GtkWidget *label;
  1885. label = gtk_label_new(ctrl->label);
  1886. shortcut_add(scs, label, ctrl->editbox.shortcut,
  1887. SHORTCUT_FOCUS, uc->entry);
  1888. if (ctrl->editbox.percentwidth == 100) {
  1889. columns_add(cols, label,
  1890. COLUMN_START(ctrl->column),
  1891. COLUMN_SPAN(ctrl->column));
  1892. columns_force_left_align(cols, label);
  1893. } else {
  1894. GtkWidget *container = columns_new(4);
  1895. gint percentages[2];
  1896. percentages[1] = ctrl->editbox.percentwidth;
  1897. percentages[0] = 100 - ctrl->editbox.percentwidth;
  1898. columns_set_cols(COLUMNS(container), 2, percentages);
  1899. columns_add(COLUMNS(container), label, 0, 1);
  1900. columns_force_left_align(COLUMNS(container), label);
  1901. columns_add(COLUMNS(container), w, 1, 1);
  1902. columns_align_next_to(COLUMNS(container), label, w);
  1903. gtk_widget_show(w);
  1904. w = container;
  1905. }
  1906. gtk_widget_show(label);
  1907. uc->label = label;
  1908. }
  1909. break;
  1910. }
  1911. case CTRL_FILESELECT:
  1912. case CTRL_FONTSELECT: {
  1913. GtkWidget *ww;
  1914. bool just_button = (ctrl->type == CTRL_FILESELECT &&
  1915. ctrl->fileselect.just_button);
  1916. if (!just_button) {
  1917. const char *browsebtn =
  1918. (ctrl->type == CTRL_FILESELECT ?
  1919. "Browse..." : "Change...");
  1920. gint percentages[] = { 75, 25 };
  1921. w = columns_new(4);
  1922. columns_set_cols(COLUMNS(w), 2, percentages);
  1923. if (ctrl->label) {
  1924. ww = gtk_label_new(ctrl->label);
  1925. columns_add(COLUMNS(w), ww, 0, 2);
  1926. columns_force_left_align(COLUMNS(w), ww);
  1927. gtk_widget_show(ww);
  1928. shortcut_add(scs, ww,
  1929. (ctrl->type == CTRL_FILESELECT ?
  1930. ctrl->fileselect.shortcut :
  1931. ctrl->fontselect.shortcut),
  1932. SHORTCUT_UCTRL, uc);
  1933. uc->label = ww;
  1934. }
  1935. uc->entry = ww = gtk_entry_new();
  1936. #if !GTK_CHECK_VERSION(3,0,0)
  1937. {
  1938. GtkRequisition req;
  1939. gtk_widget_size_request(ww, &req);
  1940. gtk_widget_set_size_request(ww, 10, req.height);
  1941. }
  1942. #else
  1943. gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
  1944. #endif
  1945. columns_add(COLUMNS(w), ww, 0, 1);
  1946. gtk_widget_show(ww);
  1947. uc->button = ww = gtk_button_new_with_label(browsebtn);
  1948. columns_add(COLUMNS(w), ww, 1, 1);
  1949. gtk_widget_show(ww);
  1950. columns_align_next_to(COLUMNS(w), uc->entry, uc->button);
  1951. g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
  1952. G_CALLBACK(editbox_key), dp);
  1953. g_signal_connect(G_OBJECT(uc->entry), "changed",
  1954. G_CALLBACK(editbox_changed), dp);
  1955. g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
  1956. G_CALLBACK(widget_focus), dp);
  1957. } else {
  1958. uc->button = w = gtk_button_new_with_label(ctrl->label);
  1959. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1960. ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc);
  1961. gtk_widget_show(w);
  1962. }
  1963. g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
  1964. G_CALLBACK(widget_focus), dp);
  1965. g_signal_connect(G_OBJECT(uc->button), "clicked",
  1966. G_CALLBACK(filefont_clicked), dp);
  1967. break;
  1968. }
  1969. case CTRL_LISTBOX:
  1970. #if GTK_CHECK_VERSION(2,0,0)
  1971. /*
  1972. * First construct the list data store, with the right
  1973. * number of columns.
  1974. */
  1975. # if !GTK_CHECK_VERSION(2,4,0)
  1976. /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
  1977. * because combo boxes are still done the old GTK1 way.) */
  1978. if (ctrl->listbox.height > 0)
  1979. # endif
  1980. {
  1981. GType *types;
  1982. int i;
  1983. int cols;
  1984. cols = ctrl->listbox.ncols;
  1985. cols = cols ? cols : 1;
  1986. types = snewn(1 + cols, GType);
  1987. types[0] = G_TYPE_INT;
  1988. for (i = 0; i < cols; i++)
  1989. types[i+1] = G_TYPE_STRING;
  1990. uc->listmodel = gtk_list_store_newv(1 + cols, types);
  1991. sfree(types);
  1992. }
  1993. #endif
  1994. /*
  1995. * See if it's a drop-down list (non-editable combo
  1996. * box).
  1997. */
  1998. if (ctrl->listbox.height == 0) {
  1999. #if !GTK_CHECK_VERSION(2,4,0)
  2000. /*
  2001. * GTK1 and early-GTK2 option-menu style of
  2002. * drop-down list.
  2003. */
  2004. uc->optmenu = w = gtk_option_menu_new();
  2005. uc->menu = gtk_menu_new();
  2006. gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
  2007. g_object_set_data(G_OBJECT(uc->menu), "user-data",
  2008. (gpointer)uc->optmenu);
  2009. g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
  2010. G_CALLBACK(widget_focus), dp);
  2011. #else
  2012. /*
  2013. * Late-GTK2 style using a GtkComboBox.
  2014. */
  2015. GtkCellRenderer *cr;
  2016. /*
  2017. * Create a non-editable GtkComboBox (that is, not
  2018. * its subclass GtkComboBoxEntry).
  2019. */
  2020. w = gtk_combo_box_new_with_model(
  2021. GTK_TREE_MODEL(uc->listmodel));
  2022. uc->combo = w;
  2023. /*
  2024. * Tell it how to render a list item (i.e. which
  2025. * column to look at in the list model).
  2026. */
  2027. cr = gtk_cell_renderer_text_new();
  2028. gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
  2029. gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
  2030. "text", 1, NULL);
  2031. /*
  2032. * And tell it to notify us when the selection
  2033. * changes.
  2034. */
  2035. g_signal_connect(G_OBJECT(w), "changed",
  2036. G_CALLBACK(droplist_selchange), dp);
  2037. g_signal_connect(G_OBJECT(w), "focus_in_event",
  2038. G_CALLBACK(widget_focus), dp);
  2039. #endif
  2040. } else {
  2041. #if !GTK_CHECK_VERSION(2,0,0)
  2042. /*
  2043. * GTK1-style full list box.
  2044. */
  2045. uc->list = gtk_list_new();
  2046. if (ctrl->listbox.multisel == 2) {
  2047. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2048. GTK_SELECTION_EXTENDED);
  2049. } else if (ctrl->listbox.multisel == 1) {
  2050. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2051. GTK_SELECTION_MULTIPLE);
  2052. } else {
  2053. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2054. GTK_SELECTION_SINGLE);
  2055. }
  2056. w = gtk_scrolled_window_new(NULL, NULL);
  2057. gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
  2058. uc->list);
  2059. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
  2060. GTK_POLICY_NEVER,
  2061. GTK_POLICY_AUTOMATIC);
  2062. uc->adj = gtk_scrolled_window_get_vadjustment(
  2063. GTK_SCROLLED_WINDOW(w));
  2064. gtk_widget_show(uc->list);
  2065. g_signal_connect(G_OBJECT(uc->list), "selection-changed",
  2066. G_CALLBACK(list_selchange), dp);
  2067. g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
  2068. G_CALLBACK(widget_focus), dp);
  2069. /*
  2070. * Adjust the height of the scrolled window to the
  2071. * minimum given by the height parameter.
  2072. *
  2073. * This piece of guesswork is a horrid hack based
  2074. * on looking inside the GTK 1.2 sources
  2075. * (specifically gtkviewport.c, which appears to be
  2076. * the widget which provides the border around the
  2077. * scrolling area). Anyone lets me know how I can
  2078. * do this in a way which isn't at risk from GTK
  2079. * upgrades, I'd be grateful.
  2080. */
  2081. {
  2082. int edge;
  2083. edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
  2084. gtk_widget_set_size_request(
  2085. w, 10, 2*edge + (ctrl->listbox.height *
  2086. get_listitemheight(w)));
  2087. }
  2088. if (ctrl->listbox.draglist) {
  2089. /*
  2090. * GTK doesn't appear to make it easy to
  2091. * implement a proper draggable list; so
  2092. * instead I'm just going to have to put an Up
  2093. * and a Down button to the right of the actual
  2094. * list box. Ah well.
  2095. */
  2096. GtkWidget *cols, *button;
  2097. static const gint percentages[2] = { 80, 20 };
  2098. cols = columns_new(4);
  2099. columns_set_cols(COLUMNS(cols), 2, percentages);
  2100. columns_add(COLUMNS(cols), w, 0, 1);
  2101. gtk_widget_show(w);
  2102. button = gtk_button_new_with_label("Up");
  2103. columns_add(COLUMNS(cols), button, 1, 1);
  2104. gtk_widget_show(button);
  2105. g_signal_connect(G_OBJECT(button), "clicked",
  2106. G_CALLBACK(draglist_up), dp);
  2107. g_signal_connect(G_OBJECT(button), "focus_in_event",
  2108. G_CALLBACK(widget_focus), dp);
  2109. button = gtk_button_new_with_label("Down");
  2110. columns_add(COLUMNS(cols), button, 1, 1);
  2111. gtk_widget_show(button);
  2112. g_signal_connect(G_OBJECT(button), "clicked",
  2113. G_CALLBACK(draglist_down), dp);
  2114. g_signal_connect(G_OBJECT(button), "focus_in_event",
  2115. G_CALLBACK(widget_focus), dp);
  2116. w = cols;
  2117. }
  2118. #else
  2119. /*
  2120. * GTK2 treeview-based full list box.
  2121. */
  2122. GtkTreeSelection *sel;
  2123. /*
  2124. * Create the list box itself, its columns, and
  2125. * its containing scrolled window.
  2126. */
  2127. w = gtk_tree_view_new_with_model(
  2128. GTK_TREE_MODEL(uc->listmodel));
  2129. g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
  2130. (gpointer)w);
  2131. gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
  2132. sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
  2133. gtk_tree_selection_set_mode(
  2134. sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
  2135. GTK_SELECTION_SINGLE);
  2136. uc->treeview = w;
  2137. g_signal_connect(G_OBJECT(w), "row-activated",
  2138. G_CALLBACK(listbox_doubleclick), dp);
  2139. g_signal_connect(G_OBJECT(w), "focus_in_event",
  2140. G_CALLBACK(widget_focus), dp);
  2141. g_signal_connect(G_OBJECT(sel), "changed",
  2142. G_CALLBACK(listbox_selchange), dp);
  2143. if (ctrl->listbox.draglist) {
  2144. gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true);
  2145. g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
  2146. G_CALLBACK(listbox_reorder), dp);
  2147. }
  2148. {
  2149. int i;
  2150. int cols;
  2151. cols = ctrl->listbox.ncols;
  2152. cols = cols ? cols : 1;
  2153. for (i = 0; i < cols; i++) {
  2154. GtkTreeViewColumn *column;
  2155. GtkCellRenderer *cellrend;
  2156. /*
  2157. * It appears that GTK 2 doesn't leave us any
  2158. * particularly sensible way to honour the
  2159. * "percentages" specification in the ctrl
  2160. * structure.
  2161. */
  2162. cellrend = gtk_cell_renderer_text_new();
  2163. if (!ctrl->listbox.hscroll) {
  2164. g_object_set(G_OBJECT(cellrend),
  2165. "ellipsize", PANGO_ELLIPSIZE_END,
  2166. "ellipsize-set", true,
  2167. (const char *)NULL);
  2168. }
  2169. column = gtk_tree_view_column_new_with_attributes(
  2170. "heading", cellrend, "text", i+1, (char *)NULL);
  2171. gtk_tree_view_column_set_sizing(
  2172. column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
  2173. gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
  2174. }
  2175. }
  2176. {
  2177. GtkWidget *scroll;
  2178. scroll = gtk_scrolled_window_new(NULL, NULL);
  2179. gtk_scrolled_window_set_shadow_type(
  2180. GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
  2181. gtk_widget_show(w);
  2182. gtk_container_add(GTK_CONTAINER(scroll), w);
  2183. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
  2184. GTK_POLICY_AUTOMATIC,
  2185. GTK_POLICY_ALWAYS);
  2186. gtk_widget_set_size_request(
  2187. scroll, -1,
  2188. ctrl->listbox.height * get_listitemheight(w));
  2189. w = scroll;
  2190. }
  2191. #endif
  2192. }
  2193. if (ctrl->label) {
  2194. GtkWidget *label, *container;
  2195. label = gtk_label_new(ctrl->label);
  2196. #if GTK_CHECK_VERSION(3,0,0)
  2197. gtk_label_set_width_chars(GTK_LABEL(label), 3);
  2198. #endif
  2199. shortcut_add(scs, label, ctrl->listbox.shortcut,
  2200. SHORTCUT_UCTRL, uc);
  2201. container = columns_new(4);
  2202. if (ctrl->listbox.percentwidth == 100) {
  2203. columns_add(COLUMNS(container), label, 0, 1);
  2204. columns_force_left_align(COLUMNS(container), label);
  2205. columns_add(COLUMNS(container), w, 0, 1);
  2206. } else {
  2207. gint percentages[2];
  2208. percentages[1] = ctrl->listbox.percentwidth;
  2209. percentages[0] = 100 - ctrl->listbox.percentwidth;
  2210. columns_set_cols(COLUMNS(container), 2, percentages);
  2211. columns_add(COLUMNS(container), label, 0, 1);
  2212. columns_force_left_align(COLUMNS(container), label);
  2213. columns_add(COLUMNS(container), w, 1, 1);
  2214. columns_align_next_to(COLUMNS(container), label, w);
  2215. }
  2216. gtk_widget_show(label);
  2217. gtk_widget_show(w);
  2218. w = container;
  2219. uc->label = label;
  2220. }
  2221. break;
  2222. case CTRL_TEXT:
  2223. #if !GTK_CHECK_VERSION(3,0,0)
  2224. /*
  2225. * Wrapping text widgets don't sit well with the GTK2
  2226. * layout model, in which widgets state a minimum size
  2227. * and the whole window then adjusts to the smallest
  2228. * size it can sensibly take given its contents. A
  2229. * wrapping text widget _has_ no clear minimum size;
  2230. * instead it has a range of possibilities. It can be
  2231. * one line deep but 2000 wide, or two lines deep and
  2232. * 1000 pixels, or three by 867, or four by 500 and so
  2233. * on. It can be as short as you like provided you
  2234. * don't mind it being wide, or as narrow as you like
  2235. * provided you don't mind it being tall.
  2236. *
  2237. * Therefore, it fits very badly into the layout model.
  2238. * Hence the only thing to do is pick a width and let
  2239. * it choose its own number of lines. To do this I'm
  2240. * going to cheat a little. All new wrapping text
  2241. * widgets will be created with a minimal text content
  2242. * "X"; then, after the rest of the dialog box is set
  2243. * up and its size calculated, the text widgets will be
  2244. * told their width and given their real text, which
  2245. * will cause the size to be recomputed in the y
  2246. * direction (because many of them will expand to more
  2247. * than one line).
  2248. */
  2249. uc->text = w = gtk_label_new("X");
  2250. uc->textsig =
  2251. g_signal_connect(G_OBJECT(w), "size-allocate",
  2252. G_CALLBACK(label_sizealloc), dp);
  2253. #else
  2254. /*
  2255. * In GTK3, this is all fixed, because the main aim of the
  2256. * new 'height-for-width' geometry management is to make
  2257. * wrapping labels behave sensibly. So now we can just do
  2258. * the obvious thing.
  2259. */
  2260. uc->text = w = gtk_label_new(uc->ctrl->label);
  2261. #endif
  2262. #if GTK_CHECK_VERSION(2,0,0)
  2263. gtk_label_set_selectable(GTK_LABEL(w), true);
  2264. gtk_widget_set_can_focus(w, false);
  2265. #endif
  2266. align_label_left(GTK_LABEL(w));
  2267. gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap);
  2268. if (!ctrl->text.wrap) {
  2269. gtk_widget_show(uc->text);
  2270. w = gtk_scrolled_window_new(NULL, NULL);
  2271. gtk_container_set_border_width(GTK_CONTAINER(w), 0);
  2272. gtk_container_add(GTK_CONTAINER(w), uc->text);
  2273. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
  2274. GTK_POLICY_AUTOMATIC,
  2275. GTK_POLICY_NEVER);
  2276. #if GTK_CHECK_VERSION(2,0,0)
  2277. gtk_widget_set_can_focus(w, false);
  2278. #endif
  2279. }
  2280. break;
  2281. }
  2282. assert(w != NULL);
  2283. columns_add(cols, w,
  2284. COLUMN_START(ctrl->column),
  2285. COLUMN_SPAN(ctrl->column));
  2286. if (left)
  2287. columns_force_left_align(cols, w);
  2288. if (ctrl->align_next_to) {
  2289. struct uctrl *uc2 = dlg_find_byctrl(
  2290. dp, ctrl->align_next_to);
  2291. assert(uc2);
  2292. columns_align_next_to(cols, w, uc2->toplevel);
  2293. #if GTK_CHECK_VERSION(3, 10, 0)
  2294. /* Slightly nicer to align baselines than just vertically
  2295. * centring, where the option is available */
  2296. gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
  2297. gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE);
  2298. #endif
  2299. }
  2300. gtk_widget_show(w);
  2301. uc->toplevel = w;
  2302. dlg_add_uctrl(dp, uc);
  2303. }
  2304. return ret;
  2305. }
  2306. struct selparam {
  2307. struct dlgparam *dp;
  2308. GtkNotebook *panels;
  2309. GtkWidget *panel;
  2310. #if !GTK_CHECK_VERSION(2,0,0)
  2311. GtkWidget *treeitem;
  2312. #else
  2313. int depth;
  2314. GtkTreePath *treepath;
  2315. #endif
  2316. struct Shortcuts shortcuts;
  2317. };
  2318. #if GTK_CHECK_VERSION(2,0,0)
  2319. static void treeselection_changed(GtkTreeSelection *treeselection,
  2320. gpointer data)
  2321. {
  2322. struct selparam **sps = (struct selparam **)data, *sp;
  2323. GtkTreeModel *treemodel;
  2324. GtkTreeIter treeiter;
  2325. gint spindex;
  2326. gint page_num;
  2327. if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
  2328. return;
  2329. gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
  2330. sp = sps[spindex];
  2331. page_num = gtk_notebook_page_num(sp->panels, sp->panel);
  2332. gtk_notebook_set_current_page(sp->panels, page_num);
  2333. sp->dp->curr_panel = sp;
  2334. dlg_refresh(NULL, sp->dp);
  2335. sp->dp->shortcuts = &sp->shortcuts;
  2336. }
  2337. #else
  2338. static void treeitem_sel(GtkItem *item, gpointer data)
  2339. {
  2340. struct selparam *sp = (struct selparam *)data;
  2341. gint page_num;
  2342. page_num = gtk_notebook_page_num(sp->panels, sp->panel);
  2343. gtk_notebook_set_page(sp->panels, page_num);
  2344. sp->dp->curr_panel = sp;
  2345. dlg_refresh(NULL, sp->dp);
  2346. sp->dp->shortcuts = &sp->shortcuts;
  2347. sp->dp->currtreeitem = sp->treeitem;
  2348. }
  2349. #endif
  2350. bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp)
  2351. {
  2352. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  2353. /*
  2354. * A control is visible if it belongs to _no_ notebook page (i.e.
  2355. * it's one of the config-box-global buttons like Load or About),
  2356. * or if it belongs to the currently selected page.
  2357. */
  2358. return uc->sp == NULL || uc->sp == dp->curr_panel;
  2359. }
  2360. #if !GTK_CHECK_VERSION(2,0,0)
  2361. static bool tree_grab_focus(struct dlgparam *dp)
  2362. {
  2363. int i, f;
  2364. /*
  2365. * See if any of the treeitems has the focus.
  2366. */
  2367. f = -1;
  2368. for (i = 0; i < dp->ntreeitems; i++)
  2369. if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
  2370. f = i;
  2371. break;
  2372. }
  2373. if (f >= 0)
  2374. return false;
  2375. else {
  2376. gtk_widget_grab_focus(dp->currtreeitem);
  2377. return true;
  2378. }
  2379. }
  2380. gint tree_focus(GtkContainer *container, GtkDirectionType direction,
  2381. gpointer data)
  2382. {
  2383. struct dlgparam *dp = (struct dlgparam *)data;
  2384. g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
  2385. /*
  2386. * If there's a focused treeitem, we return false to cause the
  2387. * focus to move on to some totally other control. If not, we
  2388. * focus the selected one.
  2389. */
  2390. return tree_grab_focus(dp);
  2391. }
  2392. #endif
  2393. gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
  2394. {
  2395. struct dlgparam *dp = (struct dlgparam *)data;
  2396. if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
  2397. g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
  2398. return true;
  2399. }
  2400. if ((event->state & GDK_MOD1_MASK) &&
  2401. (unsigned char)event->string[0] > 0 &&
  2402. (unsigned char)event->string[0] <= 127) {
  2403. int schr = (unsigned char)event->string[0];
  2404. struct Shortcut *sc = &dp->shortcuts->sc[schr];
  2405. switch (sc->action) {
  2406. case SHORTCUT_TREE:
  2407. #if GTK_CHECK_VERSION(2,0,0)
  2408. gtk_widget_grab_focus(sc->widget);
  2409. #else
  2410. tree_grab_focus(dp);
  2411. #endif
  2412. break;
  2413. case SHORTCUT_FOCUS:
  2414. gtk_widget_grab_focus(sc->widget);
  2415. break;
  2416. case SHORTCUT_UCTRL:
  2417. /*
  2418. * We must do something sensible with a uctrl.
  2419. * Precisely what this is depends on the type of
  2420. * control.
  2421. */
  2422. switch (sc->uc->ctrl->type) {
  2423. case CTRL_CHECKBOX:
  2424. case CTRL_BUTTON:
  2425. /* Check boxes and buttons get the focus _and_ get toggled. */
  2426. gtk_widget_grab_focus(sc->uc->toplevel);
  2427. g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
  2428. break;
  2429. case CTRL_FILESELECT:
  2430. case CTRL_FONTSELECT:
  2431. /* File/font selectors have their buttons pressed (ooer),
  2432. * and focus transferred to the edit box. */
  2433. g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
  2434. if (sc->uc->entry)
  2435. gtk_widget_grab_focus(sc->uc->entry);
  2436. break;
  2437. case CTRL_RADIO:
  2438. /*
  2439. * Radio buttons are fun, because they have
  2440. * multiple shortcuts. We must find whether the
  2441. * activated shortcut is the shortcut for the whole
  2442. * group, or for a particular button. In the former
  2443. * case, we find the currently selected button and
  2444. * focus it; in the latter, we focus-and-click the
  2445. * button whose shortcut was pressed.
  2446. */
  2447. if (schr == sc->uc->ctrl->radio.shortcut) {
  2448. int i;
  2449. for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
  2450. if (gtk_toggle_button_get_active(
  2451. GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
  2452. gtk_widget_grab_focus(sc->uc->buttons[i]);
  2453. }
  2454. } else if (sc->uc->ctrl->radio.shortcuts) {
  2455. int i;
  2456. for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
  2457. if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
  2458. gtk_widget_grab_focus(sc->uc->buttons[i]);
  2459. g_signal_emit_by_name(
  2460. G_OBJECT(sc->uc->buttons[i]), "clicked");
  2461. }
  2462. }
  2463. break;
  2464. case CTRL_LISTBOX:
  2465. #if !GTK_CHECK_VERSION(2,4,0)
  2466. if (sc->uc->optmenu) {
  2467. GdkEventButton bev;
  2468. gint returnval;
  2469. gtk_widget_grab_focus(sc->uc->optmenu);
  2470. /* Option menus don't work using the "clicked" signal.
  2471. * We need to manufacture a button press event :-/ */
  2472. bev.type = GDK_BUTTON_PRESS;
  2473. bev.button = 1;
  2474. g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
  2475. "button_press_event",
  2476. &bev, &returnval);
  2477. break;
  2478. }
  2479. #else
  2480. if (sc->uc->combo) {
  2481. gtk_widget_grab_focus(sc->uc->combo);
  2482. gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
  2483. break;
  2484. }
  2485. #endif
  2486. #if !GTK_CHECK_VERSION(2,0,0)
  2487. if (sc->uc->list) {
  2488. /*
  2489. * For GTK-1 style list boxes, we tell it to
  2490. * focus one of its children, which appears to
  2491. * do the Right Thing.
  2492. */
  2493. gtk_container_focus(GTK_CONTAINER(sc->uc->list),
  2494. GTK_DIR_TAB_FORWARD);
  2495. break;
  2496. }
  2497. #else
  2498. if (sc->uc->treeview) {
  2499. gtk_widget_grab_focus(sc->uc->treeview);
  2500. break;
  2501. }
  2502. #endif
  2503. unreachable("bad listbox type in win_key_press");
  2504. }
  2505. break;
  2506. }
  2507. }
  2508. return false;
  2509. }
  2510. #if !GTK_CHECK_VERSION(2,0,0)
  2511. gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
  2512. {
  2513. struct dlgparam *dp = (struct dlgparam *)data;
  2514. if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
  2515. event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
  2516. int dir, i, j = -1;
  2517. for (i = 0; i < dp->ntreeitems; i++)
  2518. if (widget == dp->treeitems[i])
  2519. break;
  2520. if (i < dp->ntreeitems) {
  2521. if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
  2522. dir = -1;
  2523. else
  2524. dir = +1;
  2525. while (1) {
  2526. i += dir;
  2527. if (i < 0 || i >= dp->ntreeitems)
  2528. break; /* nothing in that dir to select */
  2529. /*
  2530. * Determine if this tree item is visible.
  2531. */
  2532. {
  2533. GtkWidget *w = dp->treeitems[i];
  2534. bool vis = true;
  2535. while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
  2536. if (!GTK_WIDGET_VISIBLE(w)) {
  2537. vis = false;
  2538. break;
  2539. }
  2540. w = w->parent;
  2541. }
  2542. if (vis) {
  2543. j = i; /* got one */
  2544. break;
  2545. }
  2546. }
  2547. }
  2548. }
  2549. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2550. if (j >= 0) {
  2551. g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
  2552. gtk_widget_grab_focus(dp->treeitems[j]);
  2553. }
  2554. return true;
  2555. }
  2556. /*
  2557. * It's nice for Left and Right to expand and collapse tree
  2558. * branches.
  2559. */
  2560. if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
  2561. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2562. gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
  2563. return true;
  2564. }
  2565. if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
  2566. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2567. gtk_tree_item_expand(GTK_TREE_ITEM(widget));
  2568. return true;
  2569. }
  2570. return false;
  2571. }
  2572. #endif
  2573. static void shortcut_highlight(GtkWidget *labelw, int chr)
  2574. {
  2575. GtkLabel *label = GTK_LABEL(labelw);
  2576. const gchar *currstr;
  2577. gchar *pattern;
  2578. int i;
  2579. #if !GTK_CHECK_VERSION(2,0,0)
  2580. {
  2581. gchar *currstr_nonconst;
  2582. gtk_label_get(label, &currstr_nonconst);
  2583. currstr = currstr_nonconst;
  2584. }
  2585. #else
  2586. currstr = gtk_label_get_text(label);
  2587. #endif
  2588. for (i = 0; currstr[i]; i++)
  2589. if (tolower((unsigned char)currstr[i]) == chr) {
  2590. pattern = dupprintf("%*s_", i, "");
  2591. gtk_label_set_pattern(label, pattern);
  2592. sfree(pattern);
  2593. break;
  2594. }
  2595. }
  2596. void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
  2597. int chr, int action, void *ptr)
  2598. {
  2599. if (chr == NO_SHORTCUT)
  2600. return;
  2601. chr = tolower((unsigned char)chr);
  2602. assert(scs->sc[chr].action == SHORTCUT_EMPTY);
  2603. scs->sc[chr].action = action;
  2604. if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
  2605. scs->sc[chr].uc = NULL;
  2606. scs->sc[chr].widget = (GtkWidget *)ptr;
  2607. } else {
  2608. scs->sc[chr].widget = NULL;
  2609. scs->sc[chr].uc = (struct uctrl *)ptr;
  2610. }
  2611. shortcut_highlight(labelw, chr);
  2612. }
  2613. static int get_listitemheight(GtkWidget *w)
  2614. {
  2615. #if !GTK_CHECK_VERSION(2,0,0)
  2616. GtkWidget *listitem = gtk_list_item_new_with_label("foo");
  2617. GtkRequisition req;
  2618. gtk_widget_size_request(listitem, &req);
  2619. g_object_ref_sink(G_OBJECT(listitem));
  2620. return req.height;
  2621. #else
  2622. int height;
  2623. GtkCellRenderer *cr = gtk_cell_renderer_text_new();
  2624. #if GTK_CHECK_VERSION(3,0,0)
  2625. {
  2626. GtkRequisition req;
  2627. /*
  2628. * Since none of my list items wraps in this GUI, no
  2629. * interesting width-for-height behaviour should be happening,
  2630. * so I don't think it should matter here whether I ask for
  2631. * the minimum or natural height.
  2632. */
  2633. gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
  2634. height = req.height;
  2635. }
  2636. #else
  2637. gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
  2638. #endif
  2639. g_object_ref(G_OBJECT(cr));
  2640. g_object_ref_sink(G_OBJECT(cr));
  2641. g_object_unref(G_OBJECT(cr));
  2642. return height;
  2643. #endif
  2644. }
  2645. #if GTK_CHECK_VERSION(2,0,0)
  2646. void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
  2647. {
  2648. /*
  2649. * Collapse the deeper branches of the treeview into the state we
  2650. * like them to start off in. See comment below in do_config_box.
  2651. */
  2652. int i;
  2653. for (i = 0; i < dp->nselparams; i++)
  2654. if (dp->selparams[i]->depth >= 2)
  2655. gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
  2656. dp->selparams[i]->treepath);
  2657. }
  2658. #endif
  2659. #if GTK_CHECK_VERSION(3,0,0)
  2660. void treeview_map_event(GtkWidget *tree, gpointer data)
  2661. {
  2662. struct dlgparam *dp = (struct dlgparam *)data;
  2663. GtkAllocation alloc;
  2664. gtk_widget_get_allocation(tree, &alloc);
  2665. gtk_widget_set_size_request(tree, alloc.width, -1);
  2666. initial_treeview_collapse(dp, tree);
  2667. }
  2668. #endif
  2669. GtkWidget *create_config_box(const char *title, Conf *conf,
  2670. bool midsession, int protcfginfo,
  2671. post_dialog_fn_t after, void *afterctx)
  2672. {
  2673. GtkWidget *window, *hbox, *vbox, *cols, *label,
  2674. *tree, *treescroll, *panels, *panelvbox;
  2675. int index, level, protocol;
  2676. char *path;
  2677. #if GTK_CHECK_VERSION(2,0,0)
  2678. GtkTreeStore *treestore;
  2679. GtkCellRenderer *treerenderer;
  2680. GtkTreeViewColumn *treecolumn;
  2681. GtkTreeSelection *treeselection;
  2682. GtkTreeIter treeiterlevels[8];
  2683. #else
  2684. GtkTreeItem *treeitemlevels[8];
  2685. GtkTree *treelevels[8];
  2686. #endif
  2687. struct dlgparam *dp;
  2688. struct Shortcuts scs;
  2689. struct selparam **selparams = NULL;
  2690. size_t nselparams = 0, selparamsize = 0;
  2691. dp = snew(struct dlgparam);
  2692. dp->after = after;
  2693. dp->afterctx = afterctx;
  2694. dlg_init(dp);
  2695. for (index = 0; index < lenof(scs.sc); index++) {
  2696. scs.sc[index].action = SHORTCUT_EMPTY;
  2697. }
  2698. window = our_dialog_new();
  2699. dp->ctrlbox = ctrl_new_box();
  2700. protocol = conf_get_int(conf, CONF_protocol);
  2701. setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo);
  2702. unix_setup_config_box(dp->ctrlbox, midsession, protocol);
  2703. gtk_setup_config_box(dp->ctrlbox, midsession, window);
  2704. gtk_window_set_title(GTK_WINDOW(window), title);
  2705. hbox = gtk_hbox_new(false, 4);
  2706. our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, true, true, 0);
  2707. gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
  2708. gtk_widget_show(hbox);
  2709. vbox = gtk_vbox_new(false, 4);
  2710. gtk_box_pack_start(GTK_BOX(hbox), vbox, false, false, 0);
  2711. gtk_widget_show(vbox);
  2712. cols = columns_new(4);
  2713. gtk_box_pack_start(GTK_BOX(vbox), cols, false, false, 0);
  2714. gtk_widget_show(cols);
  2715. label = gtk_label_new("Category:");
  2716. columns_add(COLUMNS(cols), label, 0, 1);
  2717. columns_force_left_align(COLUMNS(cols), label);
  2718. gtk_widget_show(label);
  2719. treescroll = gtk_scrolled_window_new(NULL, NULL);
  2720. #if GTK_CHECK_VERSION(2,0,0)
  2721. treestore = gtk_tree_store_new(
  2722. TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
  2723. tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
  2724. gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false);
  2725. treerenderer = gtk_cell_renderer_text_new();
  2726. treecolumn = gtk_tree_view_column_new_with_attributes(
  2727. "Label", treerenderer, "text", 0, NULL);
  2728. gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
  2729. treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  2730. gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
  2731. gtk_container_add(GTK_CONTAINER(treescroll), tree);
  2732. #else
  2733. tree = gtk_tree_new();
  2734. gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
  2735. gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
  2736. g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp);
  2737. #endif
  2738. g_signal_connect(G_OBJECT(tree), "focus_in_event",
  2739. G_CALLBACK(widget_focus), dp);
  2740. shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
  2741. gtk_widget_show(treescroll);
  2742. gtk_box_pack_start(GTK_BOX(vbox), treescroll, true, true, 0);
  2743. panels = gtk_notebook_new();
  2744. gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false);
  2745. gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false);
  2746. gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0);
  2747. gtk_widget_show(panels);
  2748. panelvbox = NULL;
  2749. path = NULL;
  2750. level = 0;
  2751. for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
  2752. struct controlset *s = dp->ctrlbox->ctrlsets[index];
  2753. GtkWidget *w;
  2754. if (!*s->pathname) {
  2755. w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window));
  2756. our_dialog_set_action_area(GTK_WINDOW(window), w);
  2757. } else {
  2758. int j = path ? ctrl_path_compare(s->pathname, path) : 0;
  2759. if (j != INT_MAX) { /* add to treeview, start new panel */
  2760. char *c;
  2761. #if GTK_CHECK_VERSION(2,0,0)
  2762. GtkTreeIter treeiter;
  2763. #else
  2764. GtkWidget *treeitem;
  2765. #endif
  2766. bool first;
  2767. /*
  2768. * We expect never to find an implicit path
  2769. * component. For example, we expect never to see
  2770. * A/B/C followed by A/D/E, because that would
  2771. * _implicitly_ create A/D. All our path prefixes
  2772. * are expected to contain actual controls and be
  2773. * selectable in the treeview; so we would expect
  2774. * to see A/D _explicitly_ before encountering
  2775. * A/D/E.
  2776. */
  2777. assert(j == ctrl_path_elements(s->pathname) - 1);
  2778. c = strrchr(s->pathname, '/');
  2779. if (!c)
  2780. c = s->pathname;
  2781. else
  2782. c++;
  2783. path = s->pathname;
  2784. first = (panelvbox == NULL);
  2785. panelvbox = gtk_vbox_new(false, 4);
  2786. gtk_widget_show(panelvbox);
  2787. gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
  2788. NULL);
  2789. struct selparam *sp = snew(struct selparam);
  2790. if (first) {
  2791. gint page_num;
  2792. page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
  2793. panelvbox);
  2794. gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
  2795. page_num);
  2796. dp->curr_panel = sp;
  2797. }
  2798. sgrowarray(selparams, selparamsize, nselparams);
  2799. selparams[nselparams] = sp;
  2800. sp->dp = dp;
  2801. sp->panels = GTK_NOTEBOOK(panels);
  2802. sp->panel = panelvbox;
  2803. sp->shortcuts = scs; /* structure copy */
  2804. assert(j-1 < level);
  2805. #if GTK_CHECK_VERSION(2,0,0)
  2806. if (j > 0)
  2807. /* treeiterlevels[j-1] will always be valid because we
  2808. * don't allow implicit path components; see above.
  2809. */
  2810. gtk_tree_store_append(treestore, &treeiter,
  2811. &treeiterlevels[j-1]);
  2812. else
  2813. gtk_tree_store_append(treestore, &treeiter, NULL);
  2814. gtk_tree_store_set(treestore, &treeiter,
  2815. TREESTORE_PATH, c,
  2816. TREESTORE_PARAMS, nselparams,
  2817. -1);
  2818. treeiterlevels[j] = treeiter;
  2819. sp->depth = j;
  2820. if (j > 0) {
  2821. sp->treepath = gtk_tree_model_get_path(
  2822. GTK_TREE_MODEL(treestore), &treeiterlevels[j-1]);
  2823. /*
  2824. * We are going to collapse all tree branches
  2825. * at depth greater than 2, but not _yet_; see
  2826. * the comment at the call to
  2827. * gtk_tree_view_collapse_row below.
  2828. */
  2829. gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
  2830. sp->treepath, false);
  2831. } else {
  2832. sp->treepath = NULL;
  2833. }
  2834. #else
  2835. treeitem = gtk_tree_item_new_with_label(c);
  2836. if (j > 0) {
  2837. if (!treelevels[j-1]) {
  2838. treelevels[j-1] = GTK_TREE(gtk_tree_new());
  2839. gtk_tree_item_set_subtree(
  2840. treeitemlevels[j-1], GTK_WIDGET(treelevels[j-1]));
  2841. if (j < 2)
  2842. gtk_tree_item_expand(treeitemlevels[j-1]);
  2843. else
  2844. gtk_tree_item_collapse(treeitemlevels[j-1]);
  2845. }
  2846. gtk_tree_append(treelevels[j-1], treeitem);
  2847. } else {
  2848. gtk_tree_append(GTK_TREE(tree), treeitem);
  2849. }
  2850. treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
  2851. treelevels[j] = NULL;
  2852. g_signal_connect(G_OBJECT(treeitem), "key_press_event",
  2853. G_CALLBACK(tree_key_press), dp);
  2854. g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
  2855. G_CALLBACK(widget_focus), dp);
  2856. gtk_widget_show(treeitem);
  2857. if (first)
  2858. gtk_tree_select_child(GTK_TREE(tree), treeitem);
  2859. sp->treeitem = treeitem;
  2860. #endif
  2861. level = j+1;
  2862. nselparams++;
  2863. }
  2864. w = layout_ctrls(dp, selparams[nselparams-1],
  2865. &selparams[nselparams-1]->shortcuts, s, NULL);
  2866. gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0);
  2867. gtk_widget_show(w);
  2868. }
  2869. }
  2870. #if GTK_CHECK_VERSION(2,0,0)
  2871. /*
  2872. * We want our tree view to come up with all branches at depth 2
  2873. * or more collapsed. However, if we start off with those branches
  2874. * collapsed, then the tree view's size request will be calculated
  2875. * based on the width of the collapsed tree, and then when the
  2876. * collapsed branches are expanded later, the tree view will
  2877. * jarringly change size.
  2878. *
  2879. * So instead we start with everything expanded; then, once the
  2880. * tree view has computed its resulting width requirement, we
  2881. * collapse the relevant rows, but force the width to be the value
  2882. * we just retrieved. This arranges that the tree view is wide
  2883. * enough to have all branches expanded without further resizing.
  2884. */
  2885. dp->nselparams = nselparams;
  2886. dp->selparams = selparams;
  2887. #if !GTK_CHECK_VERSION(3,0,0)
  2888. {
  2889. /*
  2890. * In GTK2, we can just do the job right now.
  2891. */
  2892. GtkRequisition req;
  2893. gtk_widget_size_request(tree, &req);
  2894. initial_treeview_collapse(dp, tree);
  2895. gtk_widget_set_size_request(tree, req.width, -1);
  2896. }
  2897. #else
  2898. /*
  2899. * But in GTK3, we have to wait until the widget is about to be
  2900. * mapped, because the size computation won't have been done yet.
  2901. */
  2902. g_signal_connect(G_OBJECT(tree), "map",
  2903. G_CALLBACK(treeview_map_event), dp);
  2904. #endif /* GTK 2 vs 3 */
  2905. #endif /* GTK 2+ vs 1 */
  2906. #if GTK_CHECK_VERSION(2,0,0)
  2907. g_signal_connect(G_OBJECT(treeselection), "changed",
  2908. G_CALLBACK(treeselection_changed), selparams);
  2909. #else
  2910. dp->ntreeitems = nselparams;
  2911. dp->treeitems = snewn(dp->ntreeitems, GtkWidget *);
  2912. for (index = 0; index < nselparams; index++) {
  2913. g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select",
  2914. G_CALLBACK(treeitem_sel),
  2915. selparams[index]);
  2916. dp->treeitems[index] = selparams[index]->treeitem;
  2917. }
  2918. #endif
  2919. dp->data = conf;
  2920. dlg_refresh(NULL, dp);
  2921. dp->shortcuts = &selparams[0]->shortcuts;
  2922. #if !GTK_CHECK_VERSION(2,0,0)
  2923. dp->currtreeitem = dp->treeitems[0];
  2924. #endif
  2925. dp->lastfocus = NULL;
  2926. dp->retval = -1;
  2927. dp->window = window;
  2928. set_window_icon(window, cfg_icon, n_cfg_icon);
  2929. #if !GTK_CHECK_VERSION(2,0,0)
  2930. gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
  2931. tree);
  2932. #endif
  2933. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
  2934. GTK_POLICY_NEVER,
  2935. GTK_POLICY_AUTOMATIC);
  2936. gtk_widget_show(tree);
  2937. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  2938. gtk_widget_show(window);
  2939. /*
  2940. * Set focus into the first available control.
  2941. */
  2942. for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
  2943. struct controlset *s = dp->ctrlbox->ctrlsets[index];
  2944. bool done = false;
  2945. int j;
  2946. if (*s->pathname) {
  2947. for (j = 0; j < s->ncontrols; j++)
  2948. if (s->ctrls[j]->type != CTRL_TABDELAY &&
  2949. s->ctrls[j]->type != CTRL_COLUMNS &&
  2950. s->ctrls[j]->type != CTRL_TEXT) {
  2951. dlg_set_focus(s->ctrls[j], dp);
  2952. dp->lastfocus = s->ctrls[j];
  2953. done = true;
  2954. break;
  2955. }
  2956. }
  2957. if (done)
  2958. break;
  2959. }
  2960. g_signal_connect(G_OBJECT(window), "destroy",
  2961. G_CALLBACK(dlgparam_destroy), dp);
  2962. g_signal_connect(G_OBJECT(window), "key_press_event",
  2963. G_CALLBACK(win_key_press), dp);
  2964. return window;
  2965. }
  2966. static void dlgparam_destroy(GtkWidget *widget, gpointer data)
  2967. {
  2968. struct dlgparam *dp = (struct dlgparam *)data;
  2969. dp->after(dp->afterctx, dp->retval);
  2970. dlg_cleanup(dp);
  2971. ctrl_free_box(dp->ctrlbox);
  2972. #if GTK_CHECK_VERSION(2,0,0)
  2973. if (dp->selparams) {
  2974. for (size_t i = 0; i < dp->nselparams; i++) {
  2975. if (dp->selparams[i]->treepath)
  2976. gtk_tree_path_free(dp->selparams[i]->treepath);
  2977. sfree(dp->selparams[i]);
  2978. }
  2979. sfree(dp->selparams);
  2980. dp->selparams = NULL;
  2981. }
  2982. #endif
  2983. /*
  2984. * Instead of freeing dp right now, defer it until we return to
  2985. * the GTK main loop. Then if any other last-minute GTK events
  2986. * happen while the rest of the widgets are being cleaned up, our
  2987. * handlers will still be able to try to look things up in dp.
  2988. * (They won't find anything - we've just emptied it - but at
  2989. * least they won't crash while trying.)
  2990. */
  2991. queue_toplevel_callback(sfree, dp);
  2992. }
  2993. static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp,
  2994. void *data, int event)
  2995. {
  2996. if (event == EVENT_ACTION)
  2997. dlg_end(dp, ctrl->context.i);
  2998. }
  2999. static const struct message_box_button button_array_yn[] = {
  3000. {"Yes", 'y', +1, 1},
  3001. {"No", 'n', -1, 0},
  3002. };
  3003. const struct message_box_buttons buttons_yn = {
  3004. button_array_yn, lenof(button_array_yn),
  3005. };
  3006. static const struct message_box_button button_array_ok[] = {
  3007. {"OK", 'o', 1, 1},
  3008. };
  3009. const struct message_box_buttons buttons_ok = {
  3010. button_array_ok, lenof(button_array_ok),
  3011. };
  3012. static GtkWidget *create_message_box_general(
  3013. GtkWidget *parentwin, const char *title, const char *msg, int minwid,
  3014. bool selectable, const struct message_box_buttons *buttons,
  3015. post_dialog_fn_t after, void *afterctx,
  3016. GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx)
  3017. {
  3018. GtkWidget *window, *w0, *w1;
  3019. struct controlset *s0, *s1;
  3020. dlgcontrol *c, *textctrl;
  3021. struct dlgparam *dp;
  3022. struct Shortcuts scs;
  3023. int i, index, ncols, min_type;
  3024. dp = snew(struct dlgparam);
  3025. dp->after = after;
  3026. dp->afterctx = afterctx;
  3027. dlg_init(dp);
  3028. for (index = 0; index < lenof(scs.sc); index++) {
  3029. scs.sc[index].action = SHORTCUT_EMPTY;
  3030. }
  3031. dp->ctrlbox = ctrl_new_box();
  3032. /*
  3033. * Count up the number of buttons and find out what kinds there
  3034. * are.
  3035. */
  3036. ncols = 0;
  3037. min_type = +1;
  3038. for (i = 0; i < buttons->nbuttons; i++) {
  3039. const struct message_box_button *button = &buttons->buttons[i];
  3040. ncols++;
  3041. if (min_type > button->type)
  3042. min_type = button->type;
  3043. assert(button->value >= 0); /* <0 means no return value available */
  3044. }
  3045. s0 = ctrl_getset(dp->ctrlbox, "", "", "");
  3046. c = ctrl_columns(s0, 2, 50, 50);
  3047. c->columns.ncols = s0->ncolumns = ncols;
  3048. c->columns.percentages = sresize(c->columns.percentages, ncols, int);
  3049. for (index = 0; index < ncols; index++)
  3050. c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
  3051. index = 0;
  3052. for (i = 0; i < buttons->nbuttons; i++) {
  3053. const struct message_box_button *button = &buttons->buttons[i];
  3054. c = ctrl_pushbutton(s0, button->title, button->shortcut,
  3055. HELPCTX(no_help), messagebox_handler,
  3056. I(button->value));
  3057. c->column = index++;
  3058. if (button->type > 0)
  3059. c->button.isdefault = true;
  3060. /* We always arrange that _some_ button is labelled as
  3061. * 'iscancel', so that pressing Escape will always cause
  3062. * win_key_press to do something. The button we choose is
  3063. * whichever has the smallest type value: this means that real
  3064. * cancel buttons (labelled -1) will be picked if one is
  3065. * there, or in cases where the options are yes/no (1,0) then
  3066. * no will be picked, and if there's only one option (a box
  3067. * that really is just showing a _message_ and not even asking
  3068. * a question) then that will be picked. */
  3069. if (button->type == min_type)
  3070. c->button.iscancel = true;
  3071. }
  3072. s1 = ctrl_getset(dp->ctrlbox, "x", "", "");
  3073. textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
  3074. window = our_dialog_new();
  3075. gtk_window_set_title(GTK_WINDOW(window), title);
  3076. w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window));
  3077. if (action_postproc)
  3078. w0 = action_postproc(w0, postproc_ctx);
  3079. our_dialog_set_action_area(GTK_WINDOW(window), w0);
  3080. gtk_widget_show(w0);
  3081. w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window));
  3082. gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
  3083. gtk_widget_set_size_request(w1, minwid+20, -1);
  3084. our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
  3085. gtk_widget_show(w1);
  3086. dp->shortcuts = &scs;
  3087. dp->lastfocus = NULL;
  3088. dp->retval = 0;
  3089. dp->window = window;
  3090. if (parentwin) {
  3091. set_transient_window_pos(parentwin, window);
  3092. gtk_window_set_transient_for(GTK_WINDOW(window),
  3093. GTK_WINDOW(parentwin));
  3094. } else
  3095. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  3096. gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
  3097. gtk_widget_show(window);
  3098. gtk_window_set_focus(GTK_WINDOW(window), NULL);
  3099. #if GTK_CHECK_VERSION(2,0,0)
  3100. if (selectable) {
  3101. /*
  3102. * GTK selectable labels have a habit of selecting their
  3103. * entire contents when they gain focus. As far as I can see,
  3104. * an individual GtkLabel has no way to turn this off - source
  3105. * diving suggests that the only configurable option for it is
  3106. * "gtk-label-select-on-focus" in the cross-application
  3107. * GtkSettings, and there's no per-label or even
  3108. * per-application override.
  3109. *
  3110. * It's ugly to have text in a message box start up all
  3111. * selected, and also it interferes with any PRIMARY selection
  3112. * you might already have had. So for this purpose we'd prefer
  3113. * that the text doesn't _start off_ selected, but it should
  3114. * be selectable later.
  3115. *
  3116. * So we make the label selectable _now_, after the widget is
  3117. * shown and the focus has already gone wherever it's going.
  3118. */
  3119. struct uctrl *uc = dlg_find_byctrl(dp, textctrl);
  3120. gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
  3121. gtk_label_set_selectable(GTK_LABEL(uc->text), true);
  3122. }
  3123. #else
  3124. (void)textctrl; /* placate warning */
  3125. #endif
  3126. #if !GTK_CHECK_VERSION(2,0,0)
  3127. dp->currtreeitem = NULL;
  3128. dp->treeitems = NULL;
  3129. #else
  3130. dp->selparams = NULL;
  3131. #endif
  3132. g_signal_connect(G_OBJECT(window), "destroy",
  3133. G_CALLBACK(dlgparam_destroy), dp);
  3134. g_signal_connect(G_OBJECT(window), "key_press_event",
  3135. G_CALLBACK(win_key_press), dp);
  3136. return window;
  3137. }
  3138. GtkWidget *create_message_box(
  3139. GtkWidget *parentwin, const char *title, const char *msg, int minwid,
  3140. bool selectable, const struct message_box_buttons *buttons,
  3141. post_dialog_fn_t after, void *afterctx)
  3142. {
  3143. return create_message_box_general(
  3144. parentwin, title, msg, minwid, selectable, buttons, after, afterctx,
  3145. NULL /* action_postproc */, NULL /* postproc_ctx */);
  3146. }
  3147. struct confirm_ssh_host_key_dialog_ctx {
  3148. char *host;
  3149. int port;
  3150. char *keytype;
  3151. char *keystr;
  3152. char *more_info;
  3153. void (*callback)(void *callback_ctx, SeatPromptResult result);
  3154. void *callback_ctx;
  3155. Seat *seat;
  3156. GtkWidget *main_dialog;
  3157. GtkWidget *more_info_dialog;
  3158. };
  3159. static void confirm_ssh_host_key_result_callback(void *vctx, int result)
  3160. {
  3161. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3162. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3163. if (result >= 0) {
  3164. SeatPromptResult logical_result;
  3165. /*
  3166. * Convert the dialog-box return value (one of three
  3167. * possibilities) into the return value we pass back to the SSH
  3168. * code (one of only two possibilities, because the SSH code
  3169. * doesn't care whether we saved the host key or not).
  3170. */
  3171. if (result == 2) {
  3172. store_host_key(ctx->seat, ctx->host, ctx->port,
  3173. ctx->keytype, ctx->keystr);
  3174. logical_result = SPR_OK;
  3175. } else if (result == 1) {
  3176. logical_result = SPR_OK;
  3177. } else {
  3178. logical_result = SPR_USER_ABORT;
  3179. }
  3180. ctx->callback(ctx->callback_ctx, logical_result);
  3181. }
  3182. /*
  3183. * Clean up this context structure, whether or not a result was
  3184. * ever actually delivered from the dialog box.
  3185. */
  3186. unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT);
  3187. if (ctx->more_info_dialog)
  3188. gtk_widget_destroy(ctx->more_info_dialog);
  3189. sfree(ctx->host);
  3190. sfree(ctx->keytype);
  3191. sfree(ctx->keystr);
  3192. sfree(ctx->more_info);
  3193. sfree(ctx);
  3194. }
  3195. static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx)
  3196. {
  3197. GtkWidget *box = gtk_hbox_new(false, 10);
  3198. gtk_widget_show(box);
  3199. gtk_box_pack_end(GTK_BOX(box), w, false, true, 0);
  3200. GtkWidget *button = gtk_button_new_with_label("More info...");
  3201. gtk_widget_show(button);
  3202. gtk_box_pack_start(GTK_BOX(box), button, false, true, 0);
  3203. *(GtkWidget **)vctx = button;
  3204. return box;
  3205. }
  3206. static void more_info_closed(void *vctx, int result)
  3207. {
  3208. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3209. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3210. ctx->more_info_dialog = NULL;
  3211. }
  3212. static void more_info_button_clicked(GtkButton *button, gpointer vctx)
  3213. {
  3214. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3215. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3216. if (ctx->more_info_dialog)
  3217. return;
  3218. ctx->more_info_dialog = create_message_box(
  3219. ctx->main_dialog, "Host key information", ctx->more_info,
  3220. string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 "
  3221. "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true,
  3222. &buttons_ok, more_info_closed, ctx);
  3223. }
  3224. const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat)
  3225. {
  3226. static const SeatDialogPromptDescriptions descs = {
  3227. .hk_accept_action = "press \"Accept\"",
  3228. .hk_connect_once_action = "press \"Connect Once\"",
  3229. .hk_cancel_action = "press \"Cancel\"",
  3230. .hk_cancel_action_Participle = "Pressing \"Cancel\"",
  3231. .weak_accept_action = "press \"Yes\"",
  3232. .weak_cancel_action = "press \"No\"",
  3233. };
  3234. return &descs;
  3235. }
  3236. /*
  3237. * Format a SeatDialogText into a strbuf, also adjusting the box width
  3238. * to cope with displayed text. Returns the dialog box title.
  3239. */
  3240. static const char *gtk_format_seatdialogtext(
  3241. SeatDialogText *text, strbuf *dlg_text, int *width)
  3242. {
  3243. const char *dlg_title = NULL;
  3244. for (SeatDialogTextItem *item = text->items,
  3245. *end = item + text->nitems; item < end; item++) {
  3246. switch (item->type) {
  3247. case SDT_PARA:
  3248. put_fmt(dlg_text, "%s\n\n", item->text);
  3249. break;
  3250. case SDT_DISPLAY: {
  3251. put_fmt(dlg_text, "%s\n\n", item->text);
  3252. int thiswidth = string_width(item->text);
  3253. if (*width < thiswidth)
  3254. *width = thiswidth;
  3255. break;
  3256. }
  3257. case SDT_SCARY_HEADING:
  3258. /* Can't change font size or weight in this context */
  3259. put_fmt(dlg_text, "%s\n\n", item->text);
  3260. break;
  3261. case SDT_TITLE:
  3262. dlg_title = item->text;
  3263. break;
  3264. default:
  3265. break;
  3266. }
  3267. }
  3268. /*
  3269. * Trim trailing newlines.
  3270. */
  3271. while (strbuf_chomp(dlg_text, '\n'));
  3272. return dlg_title;
  3273. }
  3274. SeatPromptResult gtk_seat_confirm_ssh_host_key(
  3275. Seat *seat, const char *host, int port, const char *keytype,
  3276. char *keystr, SeatDialogText *text, HelpCtx helpctx,
  3277. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3278. {
  3279. static const struct message_box_button button_array_hostkey[] = {
  3280. {"Accept", 'a', 0, 2},
  3281. {"Connect Once", 'o', 0, 1},
  3282. {"Cancel", 'c', -1, 0},
  3283. };
  3284. static const struct message_box_buttons buttons_hostkey = {
  3285. button_array_hostkey, lenof(button_array_hostkey),
  3286. };
  3287. int width = string_width("default dialog width determination string");
  3288. strbuf *dlg_text = strbuf_new();
  3289. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3290. GtkWidget *mainwin, *msgbox;
  3291. struct confirm_ssh_host_key_dialog_ctx *result_ctx =
  3292. snew(struct confirm_ssh_host_key_dialog_ctx);
  3293. result_ctx->callback = callback;
  3294. result_ctx->callback_ctx = ctx;
  3295. result_ctx->host = dupstr(host);
  3296. result_ctx->port = port;
  3297. result_ctx->keytype = dupstr(keytype);
  3298. result_ctx->keystr = dupstr(keystr);
  3299. result_ctx->seat = seat;
  3300. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3301. GtkWidget *more_info_button = NULL;
  3302. msgbox = create_message_box_general(
  3303. mainwin, dlg_title, dlg_text->s, width, true,
  3304. &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx,
  3305. add_more_info_button, &more_info_button);
  3306. result_ctx->main_dialog = msgbox;
  3307. result_ctx->more_info_dialog = NULL;
  3308. strbuf *moreinfo = strbuf_new();
  3309. for (SeatDialogTextItem *item = text->items,
  3310. *end = item + text->nitems; item < end; item++) {
  3311. switch (item->type) {
  3312. case SDT_MORE_INFO_KEY:
  3313. put_fmt(moreinfo, "%s", item->text);
  3314. break;
  3315. case SDT_MORE_INFO_VALUE_SHORT:
  3316. put_fmt(moreinfo, ": %s\n", item->text);
  3317. break;
  3318. case SDT_MORE_INFO_VALUE_BLOB:
  3319. /* We have to manually wrap the public key, or else the GtkLabel
  3320. * will resize itself to accommodate the longest word, which will
  3321. * lead to a hilariously wide message box. */
  3322. put_byte(moreinfo, ':');
  3323. for (const char *p = item->text, *q = p + strlen(p); p < q ;) {
  3324. size_t linelen = q-p;
  3325. if (linelen > 72)
  3326. linelen = 72;
  3327. put_byte(moreinfo, '\n');
  3328. put_data(moreinfo, p, linelen);
  3329. p += linelen;
  3330. }
  3331. put_byte(moreinfo, '\n');
  3332. break;
  3333. default:
  3334. break;
  3335. }
  3336. }
  3337. result_ctx->more_info = strbuf_to_str(moreinfo);
  3338. g_signal_connect(G_OBJECT(more_info_button), "clicked",
  3339. G_CALLBACK(more_info_button_clicked), result_ctx);
  3340. register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox);
  3341. strbuf_free(dlg_text);
  3342. return SPR_INCOMPLETE; /* dialog still in progress */
  3343. }
  3344. struct simple_prompt_result_spr_ctx {
  3345. void (*callback)(void *callback_ctx, SeatPromptResult spr);
  3346. void *callback_ctx;
  3347. Seat *seat;
  3348. enum DialogSlot dialog_slot;
  3349. };
  3350. static void simple_prompt_result_spr_callback(void *vctx, int result)
  3351. {
  3352. struct simple_prompt_result_spr_ctx *ctx =
  3353. (struct simple_prompt_result_spr_ctx *)vctx;
  3354. unregister_dialog(ctx->seat, ctx->dialog_slot);
  3355. if (result == 0)
  3356. ctx->callback(ctx->callback_ctx, SPR_USER_ABORT);
  3357. else if (result > 0)
  3358. ctx->callback(ctx->callback_ctx, SPR_OK);
  3359. /* if <0, we're cleaning up for some other reason */
  3360. /*
  3361. * Clean up this context structure, whether or not a result was
  3362. * ever actually delivered from the dialog box.
  3363. */
  3364. sfree(ctx);
  3365. }
  3366. /*
  3367. * Ask whether the selected algorithm is acceptable (since it was
  3368. * below the configured 'warn' threshold).
  3369. */
  3370. SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
  3371. Seat *seat, SeatDialogText *text,
  3372. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3373. {
  3374. struct simple_prompt_result_spr_ctx *result_ctx;
  3375. GtkWidget *mainwin, *msgbox;
  3376. int width = string_width("Reasonably long line of text "
  3377. "as a width template");
  3378. strbuf *dlg_text = strbuf_new();
  3379. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3380. result_ctx = snew(struct simple_prompt_result_spr_ctx);
  3381. result_ctx->callback = callback;
  3382. result_ctx->callback_ctx = ctx;
  3383. result_ctx->seat = seat;
  3384. result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
  3385. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3386. msgbox = create_message_box(
  3387. mainwin, dlg_title, dlg_text->s, width, false,
  3388. &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
  3389. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3390. strbuf_free(dlg_text);
  3391. return SPR_INCOMPLETE;
  3392. }
  3393. SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
  3394. Seat *seat, SeatDialogText *text,
  3395. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3396. {
  3397. struct simple_prompt_result_spr_ctx *result_ctx;
  3398. GtkWidget *mainwin, *msgbox;
  3399. int width = string_width("is ecdsa-nistp521, which is below the configured"
  3400. " warning threshold.");
  3401. strbuf *dlg_text = strbuf_new();
  3402. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3403. result_ctx = snew(struct simple_prompt_result_spr_ctx);
  3404. result_ctx->callback = callback;
  3405. result_ctx->callback_ctx = ctx;
  3406. result_ctx->seat = seat;
  3407. result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
  3408. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3409. msgbox = create_message_box(
  3410. mainwin, dlg_title, dlg_text->s, width, false,
  3411. &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
  3412. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3413. strbuf_free(dlg_text);
  3414. return SPR_INCOMPLETE;
  3415. }
  3416. void old_keyfile_warning(void)
  3417. {
  3418. /*
  3419. * This should never happen on Unix. We hope.
  3420. */
  3421. }
  3422. void nonfatal_message_box(void *window, const char *msg)
  3423. {
  3424. char *title = dupcat(appname, " Error");
  3425. create_message_box(
  3426. window, title, msg,
  3427. string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
  3428. false, &buttons_ok, trivial_post_dialog_fn, NULL);
  3429. sfree(title);
  3430. }
  3431. void nonfatal(const char *p, ...)
  3432. {
  3433. va_list ap;
  3434. char *msg;
  3435. va_start(ap, p);
  3436. msg = dupvprintf(p, ap);
  3437. va_end(ap);
  3438. nonfatal_message_box(NULL, msg);
  3439. sfree(msg);
  3440. }
  3441. static GtkWidget *aboutbox = NULL;
  3442. static void about_window_destroyed(GtkWidget *widget, gpointer data)
  3443. {
  3444. aboutbox = NULL;
  3445. }
  3446. static void about_close_clicked(GtkButton *button, gpointer data)
  3447. {
  3448. gtk_widget_destroy(aboutbox);
  3449. aboutbox = NULL;
  3450. }
  3451. static void about_key_press(GtkWidget *widget, GdkEventKey *event,
  3452. gpointer data)
  3453. {
  3454. if (event->keyval == GDK_KEY_Escape && aboutbox) {
  3455. gtk_widget_destroy(aboutbox);
  3456. aboutbox = NULL;
  3457. }
  3458. }
  3459. static void licence_clicked(GtkButton *button, gpointer data)
  3460. {
  3461. char *title;
  3462. title = dupcat(appname, " Licence");
  3463. assert(aboutbox != NULL);
  3464. create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"),
  3465. string_width("LONGISH LINE OF TEXT SO THE LICENCE"
  3466. " BOX ISN'T EXCESSIVELY TALL AND THIN"),
  3467. true, &buttons_ok, trivial_post_dialog_fn, NULL);
  3468. sfree(title);
  3469. }
  3470. void about_box(void *window)
  3471. {
  3472. GtkWidget *w;
  3473. GtkBox *action_area;
  3474. char *title;
  3475. if (aboutbox) {
  3476. gtk_widget_grab_focus(aboutbox);
  3477. return;
  3478. }
  3479. aboutbox = our_dialog_new();
  3480. gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
  3481. title = dupcat("About ", appname);
  3482. gtk_window_set_title(GTK_WINDOW(aboutbox), title);
  3483. sfree(title);
  3484. g_signal_connect(G_OBJECT(aboutbox), "destroy",
  3485. G_CALLBACK(about_window_destroyed), NULL);
  3486. w = gtk_button_new_with_label("Close");
  3487. gtk_widget_set_can_default(w, true);
  3488. gtk_window_set_default(GTK_WINDOW(aboutbox), w);
  3489. action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
  3490. gtk_box_pack_end(action_area, w, false, false, 0);
  3491. g_signal_connect(G_OBJECT(w), "clicked",
  3492. G_CALLBACK(about_close_clicked), NULL);
  3493. gtk_widget_show(w);
  3494. w = gtk_button_new_with_label("View Licence");
  3495. gtk_widget_set_can_default(w, true);
  3496. gtk_box_pack_end(action_area, w, false, false, 0);
  3497. g_signal_connect(G_OBJECT(w), "clicked",
  3498. G_CALLBACK(licence_clicked), NULL);
  3499. gtk_widget_show(w);
  3500. {
  3501. char *buildinfo_text = buildinfo("\n");
  3502. char *label_text = dupprintf(
  3503. "%s\n\n%s\n\n%s\n\n%s",
  3504. appname, ver, buildinfo_text,
  3505. "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved");
  3506. w = gtk_label_new(label_text);
  3507. gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
  3508. #if GTK_CHECK_VERSION(2,0,0)
  3509. gtk_label_set_selectable(GTK_LABEL(w), true);
  3510. #endif
  3511. sfree(label_text);
  3512. }
  3513. our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0);
  3514. #if GTK_CHECK_VERSION(2,0,0)
  3515. /*
  3516. * Same precautions against initial select-all as in
  3517. * create_message_box().
  3518. */
  3519. gtk_widget_grab_focus(w);
  3520. gtk_label_select_region(GTK_LABEL(w), 0, 0);
  3521. #endif
  3522. gtk_widget_show(w);
  3523. g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
  3524. G_CALLBACK(about_key_press), NULL);
  3525. set_transient_window_pos(GTK_WIDGET(window), aboutbox);
  3526. if (window)
  3527. gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
  3528. GTK_WINDOW(window));
  3529. gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
  3530. gtk_widget_show(aboutbox);
  3531. gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
  3532. }
  3533. #define LOGEVENT_INITIAL_MAX 128
  3534. #define LOGEVENT_CIRCULAR_MAX 128
  3535. struct eventlog_stuff {
  3536. GtkWidget *parentwin, *window;
  3537. struct controlbox *eventbox;
  3538. struct Shortcuts scs;
  3539. struct dlgparam dp;
  3540. dlgcontrol *listctrl;
  3541. char **events_initial;
  3542. char **events_circular;
  3543. int ninitial, ncircular, circular_first;
  3544. strbuf *seldata;
  3545. int sellen;
  3546. bool ignore_selchange;
  3547. };
  3548. static void eventlog_destroy(GtkWidget *widget, gpointer data)
  3549. {
  3550. eventlog_stuff *es = (eventlog_stuff *)data;
  3551. es->window = NULL;
  3552. dlg_cleanup(&es->dp);
  3553. ctrl_free_box(es->eventbox);
  3554. }
  3555. static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
  3556. void *data, int event)
  3557. {
  3558. if (event == EVENT_ACTION)
  3559. dlg_end(dp, 0);
  3560. }
  3561. static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp,
  3562. void *data, int event)
  3563. {
  3564. eventlog_stuff *es = (eventlog_stuff *)data;
  3565. if (event == EVENT_REFRESH) {
  3566. int i;
  3567. dlg_update_start(ctrl, dp);
  3568. dlg_listbox_clear(ctrl, dp);
  3569. for (i = 0; i < es->ninitial; i++) {
  3570. dlg_listbox_add(ctrl, dp, es->events_initial[i]);
  3571. }
  3572. for (i = 0; i < es->ncircular; i++) {
  3573. dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
  3574. }
  3575. dlg_update_done(ctrl, dp);
  3576. } else if (event == EVENT_SELCHANGE) {
  3577. int i;
  3578. /*
  3579. * If this SELCHANGE event is happening as a result of
  3580. * deliberate deselection because someone else has grabbed
  3581. * the selection, the last thing we want to do is pre-empt
  3582. * them.
  3583. */
  3584. if (es->ignore_selchange)
  3585. return;
  3586. /*
  3587. * Construct the data to use as the selection.
  3588. */
  3589. strbuf_clear(es->seldata);
  3590. for (i = 0; i < es->ninitial; i++) {
  3591. if (dlg_listbox_issel(ctrl, dp, i))
  3592. put_fmt(es->seldata, "%s\n", es->events_initial[i]);
  3593. }
  3594. for (i = 0; i < es->ncircular; i++) {
  3595. if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) {
  3596. int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX;
  3597. put_fmt(es->seldata, "%s\n", es->events_circular[j]);
  3598. }
  3599. }
  3600. if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
  3601. GDK_CURRENT_TIME)) {
  3602. gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
  3603. GDK_SELECTION_TYPE_STRING, 1);
  3604. gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
  3605. compound_text_atom, 1);
  3606. }
  3607. }
  3608. }
  3609. void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
  3610. guint info, guint time_stamp, gpointer data)
  3611. {
  3612. eventlog_stuff *es = (eventlog_stuff *)data;
  3613. gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
  3614. es->seldata->u, es->seldata->len);
  3615. }
  3616. gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
  3617. gpointer data)
  3618. {
  3619. eventlog_stuff *es = (eventlog_stuff *)data;
  3620. struct uctrl *uc;
  3621. /*
  3622. * Deselect everything in the list box.
  3623. */
  3624. uc = dlg_find_byctrl(&es->dp, es->listctrl);
  3625. es->ignore_selchange = true;
  3626. #if !GTK_CHECK_VERSION(2,0,0)
  3627. assert(uc->list);
  3628. gtk_list_unselect_all(GTK_LIST(uc->list));
  3629. #else
  3630. assert(uc->treeview);
  3631. gtk_tree_selection_unselect_all(
  3632. gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
  3633. #endif
  3634. es->ignore_selchange = false;
  3635. return true;
  3636. }
  3637. void showeventlog(eventlog_stuff *es, void *parentwin)
  3638. {
  3639. GtkWidget *window, *w0, *w1;
  3640. GtkWidget *parent = GTK_WIDGET(parentwin);
  3641. struct controlset *s0, *s1;
  3642. dlgcontrol *c;
  3643. int index;
  3644. char *title;
  3645. if (es->window) {
  3646. gtk_widget_grab_focus(es->window);
  3647. return;
  3648. }
  3649. dlg_init(&es->dp);
  3650. for (index = 0; index < lenof(es->scs.sc); index++) {
  3651. es->scs.sc[index].action = SHORTCUT_EMPTY;
  3652. }
  3653. es->eventbox = ctrl_new_box();
  3654. s0 = ctrl_getset(es->eventbox, "", "", "");
  3655. ctrl_columns(s0, 3, 33, 34, 33);
  3656. c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
  3657. eventlog_ok_handler, P(NULL));
  3658. c->column = 1;
  3659. c->button.isdefault = true;
  3660. s1 = ctrl_getset(es->eventbox, "x", "", "");
  3661. es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
  3662. eventlog_list_handler, P(es));
  3663. c->listbox.height = 10;
  3664. c->listbox.multisel = 2;
  3665. c->listbox.ncols = 3;
  3666. c->listbox.percentages = snewn(3, int);
  3667. c->listbox.percentages[0] = 25;
  3668. c->listbox.percentages[1] = 10;
  3669. c->listbox.percentages[2] = 65;
  3670. es->window = window = our_dialog_new();
  3671. title = dupcat(appname, " Event Log");
  3672. gtk_window_set_title(GTK_WINDOW(window), title);
  3673. sfree(title);
  3674. w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window));
  3675. our_dialog_set_action_area(GTK_WINDOW(window), w0);
  3676. gtk_widget_show(w0);
  3677. w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window));
  3678. gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
  3679. gtk_widget_set_size_request(
  3680. w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
  3681. "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
  3682. -1);
  3683. our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
  3684. {
  3685. struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl);
  3686. columns_vexpand(COLUMNS(w1), uc->toplevel);
  3687. }
  3688. gtk_widget_show(w1);
  3689. es->dp.data = es;
  3690. es->dp.shortcuts = &es->scs;
  3691. es->dp.lastfocus = NULL;
  3692. es->dp.retval = 0;
  3693. es->dp.window = window;
  3694. dlg_refresh(NULL, &es->dp);
  3695. if (parent) {
  3696. set_transient_window_pos(parent, window);
  3697. gtk_window_set_transient_for(GTK_WINDOW(window),
  3698. GTK_WINDOW(parent));
  3699. } else
  3700. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  3701. gtk_widget_show(window);
  3702. g_signal_connect(G_OBJECT(window), "destroy",
  3703. G_CALLBACK(eventlog_destroy), es);
  3704. g_signal_connect(G_OBJECT(window), "key_press_event",
  3705. G_CALLBACK(win_key_press), &es->dp);
  3706. g_signal_connect(G_OBJECT(window), "selection_get",
  3707. G_CALLBACK(eventlog_selection_get), es);
  3708. g_signal_connect(G_OBJECT(window), "selection_clear_event",
  3709. G_CALLBACK(eventlog_selection_clear), es);
  3710. }
  3711. eventlog_stuff *eventlogstuff_new(void)
  3712. {
  3713. eventlog_stuff *es = snew(eventlog_stuff);
  3714. memset(es, 0, sizeof(*es));
  3715. es->seldata = strbuf_new();
  3716. return es;
  3717. }
  3718. void eventlogstuff_free(eventlog_stuff *es)
  3719. {
  3720. int i;
  3721. if (es->events_initial) {
  3722. for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
  3723. sfree(es->events_initial[i]);
  3724. sfree(es->events_initial);
  3725. }
  3726. if (es->events_circular) {
  3727. for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
  3728. sfree(es->events_circular[i]);
  3729. sfree(es->events_circular);
  3730. }
  3731. strbuf_free(es->seldata);
  3732. sfree(es);
  3733. }
  3734. void logevent_dlg(eventlog_stuff *es, const char *string)
  3735. {
  3736. char timebuf[40];
  3737. struct tm tm;
  3738. char **location;
  3739. size_t i;
  3740. if (es->ninitial == 0) {
  3741. es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *);
  3742. for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
  3743. es->events_initial[i] = NULL;
  3744. es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *);
  3745. for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
  3746. es->events_circular[i] = NULL;
  3747. }
  3748. if (es->ninitial < LOGEVENT_INITIAL_MAX)
  3749. location = &es->events_initial[es->ninitial];
  3750. else
  3751. location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX];
  3752. tm=ltime();
  3753. strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
  3754. sfree(*location);
  3755. *location = dupcat(timebuf, string);
  3756. if (es->window) {
  3757. dlg_listbox_add(es->listctrl, &es->dp, *location);
  3758. }
  3759. if (es->ninitial < LOGEVENT_INITIAL_MAX) {
  3760. es->ninitial++;
  3761. } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) {
  3762. es->ncircular++;
  3763. } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) {
  3764. es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
  3765. sfree(es->events_circular[es->circular_first]);
  3766. es->events_circular[es->circular_first] = dupstr("..");
  3767. }
  3768. }
  3769. struct simple_prompt_result_int_ctx {
  3770. void (*callback)(void *callback_ctx, int result);
  3771. void *callback_ctx;
  3772. Seat *seat;
  3773. enum DialogSlot dialog_slot;
  3774. };
  3775. static void simple_prompt_result_int_callback(void *vctx, int result)
  3776. {
  3777. struct simple_prompt_result_int_ctx *ctx =
  3778. (struct simple_prompt_result_int_ctx *)vctx;
  3779. unregister_dialog(ctx->seat, ctx->dialog_slot);
  3780. if (result >= 0)
  3781. ctx->callback(ctx->callback_ctx, result);
  3782. /*
  3783. * Clean up this context structure, whether or not a result was
  3784. * ever actually delivered from the dialog box.
  3785. */
  3786. sfree(ctx);
  3787. }
  3788. int gtkdlg_askappend(Seat *seat, Filename *filename,
  3789. void (*callback)(void *ctx, int result), void *ctx)
  3790. {
  3791. static const char msgtemplate[] =
  3792. "The session log file \"%.*s\" already exists. "
  3793. "You can overwrite it with a new session log, "
  3794. "append your session log to the end of it, "
  3795. "or disable session logging for this session.";
  3796. static const struct message_box_button button_array_append[] = {
  3797. {"Overwrite", 'o', 1, 2},
  3798. {"Append", 'a', 0, 1},
  3799. {"Disable", 'd', -1, 0},
  3800. };
  3801. static const struct message_box_buttons buttons_append = {
  3802. button_array_append, lenof(button_array_append),
  3803. };
  3804. char *message;
  3805. char *mbtitle;
  3806. struct simple_prompt_result_int_ctx *result_ctx;
  3807. GtkWidget *mainwin, *msgbox;
  3808. message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
  3809. mbtitle = dupprintf("%s Log to File", appname);
  3810. result_ctx = snew(struct simple_prompt_result_int_ctx);
  3811. result_ctx->callback = callback;
  3812. result_ctx->callback_ctx = ctx;
  3813. result_ctx->seat = seat;
  3814. result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT;
  3815. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3816. msgbox = create_message_box(
  3817. mainwin, mbtitle, message,
  3818. string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"),
  3819. false, &buttons_append, simple_prompt_result_int_callback, result_ctx);
  3820. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3821. sfree(message);
  3822. sfree(mbtitle);
  3823. return -1; /* dialog still in progress */
  3824. }
  3825. struct ca_config_box {
  3826. GtkWidget *window;
  3827. struct controlbox *cb;
  3828. struct Shortcuts scs;
  3829. bool quit_main;
  3830. dlgparam dp;
  3831. };
  3832. static struct ca_config_box *cacfg; /* one of these, cross-instance */
  3833. static void cacfg_destroy(GtkWidget *widget, gpointer data)
  3834. {
  3835. cacfg->window = NULL;
  3836. dlg_cleanup(&cacfg->dp);
  3837. ctrl_free_box(cacfg->cb);
  3838. cacfg->cb = NULL;
  3839. if (cacfg->quit_main)
  3840. gtk_main_quit();
  3841. }
  3842. static void make_ca_config_box(GtkWidget *spawning_window)
  3843. {
  3844. if (!cacfg) {
  3845. cacfg = snew(struct ca_config_box);
  3846. memset(cacfg, 0, sizeof(*cacfg));
  3847. }
  3848. if (cacfg->window) {
  3849. /* This dialog box is already displayed; re-focus it */
  3850. gtk_widget_grab_focus(cacfg->window);
  3851. return;
  3852. }
  3853. dlg_init(&cacfg->dp);
  3854. for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) {
  3855. cacfg->scs.sc[i].action = SHORTCUT_EMPTY;
  3856. }
  3857. cacfg->cb = ctrl_new_box();
  3858. setup_ca_config_box(cacfg->cb);
  3859. cacfg->window = our_dialog_new();
  3860. gtk_window_set_title(GTK_WINDOW(cacfg->window),
  3861. "PuTTY trusted host certification authorities");
  3862. gtk_widget_set_size_request(
  3863. cacfg->window, string_width(
  3864. "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5"
  3865. "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1);
  3866. /* Set up everything else */
  3867. for (int i = 0; i < cacfg->cb->nctrlsets; i++) {
  3868. struct controlset *s = cacfg->cb->ctrlsets[i];
  3869. GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s,
  3870. GTK_WINDOW(cacfg->window));
  3871. gtk_container_set_border_width(GTK_CONTAINER(w), 10);
  3872. gtk_widget_show(w);
  3873. if (!*s->pathname) {
  3874. our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w);
  3875. } else {
  3876. our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w,
  3877. true, true, 0);
  3878. }
  3879. }
  3880. cacfg->dp.data = cacfg;
  3881. cacfg->dp.shortcuts = &cacfg->scs;
  3882. cacfg->dp.lastfocus = NULL;
  3883. cacfg->dp.retval = 0;
  3884. cacfg->dp.window = cacfg->window;
  3885. dlg_refresh(NULL, &cacfg->dp);
  3886. if (spawning_window) {
  3887. set_transient_window_pos(spawning_window, cacfg->window);
  3888. } else {
  3889. gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER);
  3890. }
  3891. gtk_widget_show(cacfg->window);
  3892. g_signal_connect(G_OBJECT(cacfg->window), "destroy",
  3893. G_CALLBACK(cacfg_destroy), NULL);
  3894. g_signal_connect(G_OBJECT(cacfg->window), "key_press_event",
  3895. G_CALLBACK(win_key_press), &cacfg->dp);
  3896. }
  3897. void show_ca_config_box(dlgparam *dp)
  3898. {
  3899. make_ca_config_box(dp ? dp->window : NULL);
  3900. }
  3901. void show_ca_config_box_synchronously(void)
  3902. {
  3903. make_ca_config_box(NULL);
  3904. cacfg->quit_main = true;
  3905. gtk_main();
  3906. }