dialog.c 148 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413
  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. #ifdef USE_GTK_FILE_CHOOSER_DIALOG
  1532. GtkWidget *filechoose = gtk_file_chooser_dialog_new(
  1533. uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
  1534. (uc->ctrl->fileselect.for_writing ?
  1535. GTK_FILE_CHOOSER_ACTION_SAVE :
  1536. GTK_FILE_CHOOSER_ACTION_OPEN),
  1537. STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
  1538. STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
  1539. (const gchar *)NULL);
  1540. gtk_window_set_modal(GTK_WINDOW(filechoose), true);
  1541. g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
  1542. g_signal_connect(G_OBJECT(filechoose), "response",
  1543. G_CALLBACK(filechoose_response), (gpointer)dp);
  1544. gtk_widget_show(filechoose);
  1545. #else
  1546. GtkWidget *filesel =
  1547. gtk_file_selection_new(uc->ctrl->fileselect.title);
  1548. gtk_window_set_modal(GTK_WINDOW(filesel), true);
  1549. g_object_set_data(
  1550. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
  1551. (gpointer)filesel);
  1552. g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
  1553. g_signal_connect(
  1554. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
  1555. G_CALLBACK(filesel_ok), (gpointer)dp);
  1556. g_signal_connect_swapped(
  1557. G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
  1558. G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
  1559. g_signal_connect_swapped(
  1560. G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
  1561. G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
  1562. gtk_widget_show(filesel);
  1563. #endif
  1564. }
  1565. if (uc->ctrl->type == CTRL_FONTSELECT) {
  1566. const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
  1567. #if !GTK_CHECK_VERSION(2,0,0)
  1568. /*
  1569. * Use the GTK 1 standard font selector.
  1570. */
  1571. gchar *spacings[] = { "c", "m", NULL };
  1572. GtkWidget *fontsel =
  1573. gtk_font_selection_dialog_new("Select a font");
  1574. gtk_window_set_modal(GTK_WINDOW(fontsel), true);
  1575. gtk_font_selection_dialog_set_filter(
  1576. GTK_FONT_SELECTION_DIALOG(fontsel),
  1577. GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
  1578. NULL, NULL, NULL, NULL, spacings, NULL);
  1579. if (!gtk_font_selection_dialog_set_font_name(
  1580. GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
  1581. /*
  1582. * If the font name wasn't found as it was, try opening
  1583. * it and extracting its FONT property. This should
  1584. * have the effect of mapping short aliases into true
  1585. * XLFDs.
  1586. */
  1587. GdkFont *font = gdk_font_load(fontname);
  1588. if (font) {
  1589. XFontStruct *xfs = GDK_FONT_XFONT(font);
  1590. Display *disp = get_x11_display();
  1591. Atom fontprop = XInternAtom(disp, "FONT", False);
  1592. unsigned long ret;
  1593. assert(disp); /* this is GTK1! */
  1594. gdk_font_ref(font);
  1595. if (XGetFontProperty(xfs, fontprop, &ret)) {
  1596. char *name = XGetAtomName(disp, (Atom)ret);
  1597. if (name)
  1598. gtk_font_selection_dialog_set_font_name(
  1599. GTK_FONT_SELECTION_DIALOG(fontsel), name);
  1600. }
  1601. gdk_font_unref(font);
  1602. }
  1603. }
  1604. g_object_set_data(
  1605. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1606. "user-data", (gpointer)fontsel);
  1607. g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
  1608. g_signal_connect(
  1609. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1610. "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
  1611. g_signal_connect_swapped(
  1612. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
  1613. "clicked", G_CALLBACK(gtk_widget_destroy),
  1614. (gpointer)fontsel);
  1615. g_signal_connect_swapped(
  1616. G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
  1617. "clicked", G_CALLBACK(gtk_widget_destroy),
  1618. (gpointer)fontsel);
  1619. gtk_widget_show(fontsel);
  1620. #else /* !GTK_CHECK_VERSION(2,0,0) */
  1621. /*
  1622. * Use the unifontsel code provided in unifont.c.
  1623. */
  1624. unifontsel *fontsel = unifontsel_new("Select a font");
  1625. gtk_window_set_modal(fontsel->window, true);
  1626. unifontsel_set_name(fontsel, fontname);
  1627. g_object_set_data(G_OBJECT(fontsel->ok_button),
  1628. "user-data", (gpointer)fontsel);
  1629. fontsel->user_data = uc;
  1630. g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
  1631. G_CALLBACK(fontsel_ok), (gpointer)dp);
  1632. g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
  1633. G_CALLBACK(unifontsel_destroy),
  1634. (gpointer)fontsel);
  1635. g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
  1636. G_CALLBACK(unifontsel_destroy),
  1637. (gpointer)fontsel);
  1638. gtk_widget_show(GTK_WIDGET(fontsel->window));
  1639. #endif /* !GTK_CHECK_VERSION(2,0,0) */
  1640. }
  1641. }
  1642. #if !GTK_CHECK_VERSION(3,0,0)
  1643. static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
  1644. gpointer data)
  1645. {
  1646. struct dlgparam *dp = (struct dlgparam *)data;
  1647. struct uctrl *uc = dlg_find_bywidget(dp, widget);
  1648. gtk_widget_set_size_request(uc->text, alloc->width, -1);
  1649. gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label);
  1650. g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
  1651. }
  1652. #endif
  1653. /* ----------------------------------------------------------------------
  1654. * This function does the main layout work: it reads a controlset,
  1655. * it creates the relevant GTK controls, and returns a GtkWidget
  1656. * containing the result. (This widget might be a title of some
  1657. * sort, it might be a Columns containing many controls, or it
  1658. * might be a GtkFrame containing a Columns; whatever it is, it's
  1659. * definitely a GtkWidget and should probably be added to a
  1660. * GtkVbox.)
  1661. *
  1662. * `win' is required for setting the default button. If it is
  1663. * non-NULL, all buttons created will be default-capable (so they
  1664. * have extra space round them for the default highlight).
  1665. */
  1666. GtkWidget *layout_ctrls(
  1667. struct dlgparam *dp, struct selparam *sp, struct Shortcuts *scs,
  1668. struct controlset *s, GtkWindow *win)
  1669. {
  1670. Columns *cols;
  1671. GtkWidget *ret;
  1672. int i;
  1673. if (!s->boxname) {
  1674. /* This controlset is a panel title. */
  1675. assert(s->boxtitle);
  1676. return gtk_label_new(s->boxtitle);
  1677. }
  1678. /*
  1679. * Otherwise, we expect to be laying out actual controls, so
  1680. * we'll start by creating a Columns for the purpose.
  1681. */
  1682. cols = COLUMNS(columns_new(4));
  1683. ret = GTK_WIDGET(cols);
  1684. gtk_widget_show(ret);
  1685. /*
  1686. * Create a containing frame if we have a box name.
  1687. */
  1688. if (*s->boxname) {
  1689. ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
  1690. gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
  1691. gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
  1692. gtk_widget_show(ret);
  1693. }
  1694. /*
  1695. * Now iterate through the controls themselves, create them,
  1696. * and add them to the Columns.
  1697. */
  1698. for (i = 0; i < s->ncontrols; i++) {
  1699. dlgcontrol *ctrl = s->ctrls[i];
  1700. struct uctrl *uc;
  1701. bool left = false;
  1702. GtkWidget *w = NULL;
  1703. switch (ctrl->type) {
  1704. case CTRL_COLUMNS: {
  1705. static const int simplecols[1] = { 100 };
  1706. columns_set_cols(cols, ctrl->columns.ncols,
  1707. (ctrl->columns.percentages ?
  1708. ctrl->columns.percentages : simplecols));
  1709. continue; /* no actual control created */
  1710. }
  1711. case CTRL_TABDELAY: {
  1712. struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
  1713. if (uc)
  1714. columns_taborder_last(cols, uc->toplevel);
  1715. continue; /* no actual control created */
  1716. }
  1717. }
  1718. uc = snew(struct uctrl);
  1719. uc->sp = sp;
  1720. uc->ctrl = ctrl;
  1721. uc->buttons = NULL;
  1722. uc->entry = NULL;
  1723. #if !GTK_CHECK_VERSION(2,4,0)
  1724. uc->list = uc->menu = uc->optmenu = NULL;
  1725. #else
  1726. uc->combo = NULL;
  1727. #endif
  1728. #if GTK_CHECK_VERSION(2,0,0)
  1729. uc->treeview = NULL;
  1730. uc->listmodel = NULL;
  1731. #endif
  1732. uc->button = uc->text = NULL;
  1733. uc->label = NULL;
  1734. uc->nclicks = 0;
  1735. switch (ctrl->type) {
  1736. case CTRL_BUTTON:
  1737. w = gtk_button_new_with_label(ctrl->label);
  1738. if (win) {
  1739. gtk_widget_set_can_default(w, true);
  1740. if (ctrl->button.isdefault)
  1741. gtk_window_set_default(win, w);
  1742. if (ctrl->button.iscancel)
  1743. dp->cancelbutton = w;
  1744. }
  1745. g_signal_connect(G_OBJECT(w), "clicked",
  1746. G_CALLBACK(button_clicked), dp);
  1747. g_signal_connect(G_OBJECT(w), "focus_in_event",
  1748. G_CALLBACK(widget_focus), dp);
  1749. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1750. ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
  1751. break;
  1752. case CTRL_CHECKBOX:
  1753. w = gtk_check_button_new_with_label(ctrl->label);
  1754. g_signal_connect(G_OBJECT(w), "toggled",
  1755. G_CALLBACK(button_toggled), dp);
  1756. g_signal_connect(G_OBJECT(w), "focus_in_event",
  1757. G_CALLBACK(widget_focus), dp);
  1758. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1759. ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
  1760. left = true;
  1761. break;
  1762. case CTRL_RADIO: {
  1763. /*
  1764. * Radio buttons get to go inside their own Columns, no
  1765. * matter what.
  1766. */
  1767. gint i, *percentages;
  1768. GSList *group;
  1769. w = columns_new(0);
  1770. if (ctrl->label) {
  1771. GtkWidget *label = gtk_label_new(ctrl->label);
  1772. columns_add(COLUMNS(w), label, 0, 1);
  1773. columns_force_left_align(COLUMNS(w), label);
  1774. gtk_widget_show(label);
  1775. shortcut_add(scs, label, ctrl->radio.shortcut,
  1776. SHORTCUT_UCTRL, uc);
  1777. uc->label = label;
  1778. }
  1779. percentages = g_new(gint, ctrl->radio.ncolumns);
  1780. for (i = 0; i < ctrl->radio.ncolumns; i++) {
  1781. percentages[i] =
  1782. ((100 * (i+1) / ctrl->radio.ncolumns) -
  1783. 100 * i / ctrl->radio.ncolumns);
  1784. }
  1785. columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
  1786. percentages);
  1787. g_free(percentages);
  1788. group = NULL;
  1789. uc->nbuttons = ctrl->radio.nbuttons;
  1790. uc->buttons = snewn(uc->nbuttons, GtkWidget *);
  1791. for (i = 0; i < ctrl->radio.nbuttons; i++) {
  1792. GtkWidget *b;
  1793. gint colstart;
  1794. b = gtk_radio_button_new_with_label(
  1795. group, ctrl->radio.buttons[i]);
  1796. uc->buttons[i] = b;
  1797. group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
  1798. colstart = i % ctrl->radio.ncolumns;
  1799. columns_add(COLUMNS(w), b, colstart,
  1800. (i == ctrl->radio.nbuttons-1 ?
  1801. ctrl->radio.ncolumns - colstart : 1));
  1802. columns_force_left_align(COLUMNS(w), b);
  1803. gtk_widget_show(b);
  1804. g_signal_connect(G_OBJECT(b), "toggled",
  1805. G_CALLBACK(button_toggled), dp);
  1806. g_signal_connect(G_OBJECT(b), "focus_in_event",
  1807. G_CALLBACK(widget_focus), dp);
  1808. if (ctrl->radio.shortcuts) {
  1809. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
  1810. ctrl->radio.shortcuts[i],
  1811. SHORTCUT_UCTRL, uc);
  1812. }
  1813. }
  1814. break;
  1815. }
  1816. case CTRL_EDITBOX: {
  1817. GtkWidget *signalobject;
  1818. if (ctrl->editbox.has_list) {
  1819. #if !GTK_CHECK_VERSION(2,4,0)
  1820. /*
  1821. * GTK 1 combo box.
  1822. */
  1823. w = gtk_combo_new();
  1824. gtk_combo_set_value_in_list(GTK_COMBO(w), false, true);
  1825. uc->entry = GTK_COMBO(w)->entry;
  1826. uc->list = GTK_COMBO(w)->list;
  1827. signalobject = uc->entry;
  1828. #else
  1829. /*
  1830. * GTK 2 combo box.
  1831. */
  1832. uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
  1833. G_TYPE_STRING);
  1834. w = gtk_combo_box_new_with_model_and_entry(
  1835. GTK_TREE_MODEL(uc->listmodel));
  1836. g_object_set(G_OBJECT(w), "entry-text-column", 1,
  1837. (const char *)NULL);
  1838. /* We cannot support password combo boxes. */
  1839. assert(!ctrl->editbox.password);
  1840. uc->combo = w;
  1841. signalobject = uc->combo;
  1842. #endif
  1843. } else {
  1844. w = gtk_entry_new();
  1845. if (ctrl->editbox.password)
  1846. gtk_entry_set_visibility(GTK_ENTRY(w), false);
  1847. uc->entry = w;
  1848. signalobject = w;
  1849. }
  1850. g_signal_connect(G_OBJECT(signalobject), "changed",
  1851. G_CALLBACK(editbox_changed), dp);
  1852. g_signal_connect(G_OBJECT(signalobject), "key_press_event",
  1853. G_CALLBACK(editbox_key), dp);
  1854. g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
  1855. G_CALLBACK(widget_focus), dp);
  1856. g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
  1857. G_CALLBACK(editbox_lostfocus), dp);
  1858. g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
  1859. G_CALLBACK(editbox_lostfocus), dp);
  1860. #if !GTK_CHECK_VERSION(3,0,0)
  1861. /*
  1862. * Edit boxes, for some strange reason, have a minimum
  1863. * width of 150 in GTK 1.2. We don't want this - we'd
  1864. * rather the edit boxes acquired their natural width
  1865. * from the column layout of the rest of the box.
  1866. */
  1867. {
  1868. GtkRequisition req;
  1869. gtk_widget_size_request(w, &req);
  1870. gtk_widget_set_size_request(w, 10, req.height);
  1871. }
  1872. #else
  1873. /*
  1874. * In GTK 3, this is still true, but there's a special
  1875. * method for GtkEntry in particular to fix it.
  1876. */
  1877. if (GTK_IS_ENTRY(w))
  1878. gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
  1879. #endif
  1880. if (ctrl->label) {
  1881. GtkWidget *label;
  1882. label = gtk_label_new(ctrl->label);
  1883. shortcut_add(scs, label, ctrl->editbox.shortcut,
  1884. SHORTCUT_FOCUS, uc->entry);
  1885. if (ctrl->editbox.percentwidth == 100) {
  1886. columns_add(cols, label,
  1887. COLUMN_START(ctrl->column),
  1888. COLUMN_SPAN(ctrl->column));
  1889. columns_force_left_align(cols, label);
  1890. } else {
  1891. GtkWidget *container = columns_new(4);
  1892. gint percentages[2];
  1893. percentages[1] = ctrl->editbox.percentwidth;
  1894. percentages[0] = 100 - ctrl->editbox.percentwidth;
  1895. columns_set_cols(COLUMNS(container), 2, percentages);
  1896. columns_add(COLUMNS(container), label, 0, 1);
  1897. columns_force_left_align(COLUMNS(container), label);
  1898. columns_add(COLUMNS(container), w, 1, 1);
  1899. columns_align_next_to(COLUMNS(container), label, w);
  1900. gtk_widget_show(w);
  1901. w = container;
  1902. }
  1903. gtk_widget_show(label);
  1904. uc->label = label;
  1905. }
  1906. break;
  1907. }
  1908. case CTRL_FILESELECT:
  1909. case CTRL_FONTSELECT: {
  1910. GtkWidget *ww;
  1911. bool just_button = (ctrl->type == CTRL_FILESELECT &&
  1912. ctrl->fileselect.just_button);
  1913. if (!just_button) {
  1914. const char *browsebtn =
  1915. (ctrl->type == CTRL_FILESELECT ?
  1916. "Browse..." : "Change...");
  1917. gint percentages[] = { 75, 25 };
  1918. w = columns_new(4);
  1919. columns_set_cols(COLUMNS(w), 2, percentages);
  1920. if (ctrl->label) {
  1921. ww = gtk_label_new(ctrl->label);
  1922. columns_add(COLUMNS(w), ww, 0, 2);
  1923. columns_force_left_align(COLUMNS(w), ww);
  1924. gtk_widget_show(ww);
  1925. shortcut_add(scs, ww,
  1926. (ctrl->type == CTRL_FILESELECT ?
  1927. ctrl->fileselect.shortcut :
  1928. ctrl->fontselect.shortcut),
  1929. SHORTCUT_UCTRL, uc);
  1930. uc->label = ww;
  1931. }
  1932. uc->entry = ww = gtk_entry_new();
  1933. #if !GTK_CHECK_VERSION(3,0,0)
  1934. {
  1935. GtkRequisition req;
  1936. gtk_widget_size_request(ww, &req);
  1937. gtk_widget_set_size_request(ww, 10, req.height);
  1938. }
  1939. #else
  1940. gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
  1941. #endif
  1942. columns_add(COLUMNS(w), ww, 0, 1);
  1943. gtk_widget_show(ww);
  1944. uc->button = ww = gtk_button_new_with_label(browsebtn);
  1945. columns_add(COLUMNS(w), ww, 1, 1);
  1946. gtk_widget_show(ww);
  1947. columns_align_next_to(COLUMNS(w), uc->entry, uc->button);
  1948. g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
  1949. G_CALLBACK(editbox_key), dp);
  1950. g_signal_connect(G_OBJECT(uc->entry), "changed",
  1951. G_CALLBACK(editbox_changed), dp);
  1952. g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
  1953. G_CALLBACK(widget_focus), dp);
  1954. } else {
  1955. uc->button = w = gtk_button_new_with_label(ctrl->label);
  1956. shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
  1957. ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc);
  1958. gtk_widget_show(w);
  1959. }
  1960. g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
  1961. G_CALLBACK(widget_focus), dp);
  1962. g_signal_connect(G_OBJECT(uc->button), "clicked",
  1963. G_CALLBACK(filefont_clicked), dp);
  1964. break;
  1965. }
  1966. case CTRL_LISTBOX:
  1967. #if GTK_CHECK_VERSION(2,0,0)
  1968. /*
  1969. * First construct the list data store, with the right
  1970. * number of columns.
  1971. */
  1972. # if !GTK_CHECK_VERSION(2,4,0)
  1973. /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
  1974. * because combo boxes are still done the old GTK1 way.) */
  1975. if (ctrl->listbox.height > 0)
  1976. # endif
  1977. {
  1978. GType *types;
  1979. int i;
  1980. int cols;
  1981. cols = ctrl->listbox.ncols;
  1982. cols = cols ? cols : 1;
  1983. types = snewn(1 + cols, GType);
  1984. types[0] = G_TYPE_INT;
  1985. for (i = 0; i < cols; i++)
  1986. types[i+1] = G_TYPE_STRING;
  1987. uc->listmodel = gtk_list_store_newv(1 + cols, types);
  1988. sfree(types);
  1989. }
  1990. #endif
  1991. /*
  1992. * See if it's a drop-down list (non-editable combo
  1993. * box).
  1994. */
  1995. if (ctrl->listbox.height == 0) {
  1996. #if !GTK_CHECK_VERSION(2,4,0)
  1997. /*
  1998. * GTK1 and early-GTK2 option-menu style of
  1999. * drop-down list.
  2000. */
  2001. uc->optmenu = w = gtk_option_menu_new();
  2002. uc->menu = gtk_menu_new();
  2003. gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
  2004. g_object_set_data(G_OBJECT(uc->menu), "user-data",
  2005. (gpointer)uc->optmenu);
  2006. g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
  2007. G_CALLBACK(widget_focus), dp);
  2008. #else
  2009. /*
  2010. * Late-GTK2 style using a GtkComboBox.
  2011. */
  2012. GtkCellRenderer *cr;
  2013. /*
  2014. * Create a non-editable GtkComboBox (that is, not
  2015. * its subclass GtkComboBoxEntry).
  2016. */
  2017. w = gtk_combo_box_new_with_model(
  2018. GTK_TREE_MODEL(uc->listmodel));
  2019. uc->combo = w;
  2020. /*
  2021. * Tell it how to render a list item (i.e. which
  2022. * column to look at in the list model).
  2023. */
  2024. cr = gtk_cell_renderer_text_new();
  2025. gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
  2026. gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
  2027. "text", 1, NULL);
  2028. /*
  2029. * And tell it to notify us when the selection
  2030. * changes.
  2031. */
  2032. g_signal_connect(G_OBJECT(w), "changed",
  2033. G_CALLBACK(droplist_selchange), dp);
  2034. g_signal_connect(G_OBJECT(w), "focus_in_event",
  2035. G_CALLBACK(widget_focus), dp);
  2036. #endif
  2037. } else {
  2038. #if !GTK_CHECK_VERSION(2,0,0)
  2039. /*
  2040. * GTK1-style full list box.
  2041. */
  2042. uc->list = gtk_list_new();
  2043. if (ctrl->listbox.multisel == 2) {
  2044. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2045. GTK_SELECTION_EXTENDED);
  2046. } else if (ctrl->listbox.multisel == 1) {
  2047. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2048. GTK_SELECTION_MULTIPLE);
  2049. } else {
  2050. gtk_list_set_selection_mode(GTK_LIST(uc->list),
  2051. GTK_SELECTION_SINGLE);
  2052. }
  2053. w = gtk_scrolled_window_new(NULL, NULL);
  2054. gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
  2055. uc->list);
  2056. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
  2057. GTK_POLICY_NEVER,
  2058. GTK_POLICY_AUTOMATIC);
  2059. uc->adj = gtk_scrolled_window_get_vadjustment(
  2060. GTK_SCROLLED_WINDOW(w));
  2061. gtk_widget_show(uc->list);
  2062. g_signal_connect(G_OBJECT(uc->list), "selection-changed",
  2063. G_CALLBACK(list_selchange), dp);
  2064. g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
  2065. G_CALLBACK(widget_focus), dp);
  2066. /*
  2067. * Adjust the height of the scrolled window to the
  2068. * minimum given by the height parameter.
  2069. *
  2070. * This piece of guesswork is a horrid hack based
  2071. * on looking inside the GTK 1.2 sources
  2072. * (specifically gtkviewport.c, which appears to be
  2073. * the widget which provides the border around the
  2074. * scrolling area). Anyone lets me know how I can
  2075. * do this in a way which isn't at risk from GTK
  2076. * upgrades, I'd be grateful.
  2077. */
  2078. {
  2079. int edge;
  2080. edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
  2081. gtk_widget_set_size_request(
  2082. w, 10, 2*edge + (ctrl->listbox.height *
  2083. get_listitemheight(w)));
  2084. }
  2085. if (ctrl->listbox.draglist) {
  2086. /*
  2087. * GTK doesn't appear to make it easy to
  2088. * implement a proper draggable list; so
  2089. * instead I'm just going to have to put an Up
  2090. * and a Down button to the right of the actual
  2091. * list box. Ah well.
  2092. */
  2093. GtkWidget *cols, *button;
  2094. static const gint percentages[2] = { 80, 20 };
  2095. cols = columns_new(4);
  2096. columns_set_cols(COLUMNS(cols), 2, percentages);
  2097. columns_add(COLUMNS(cols), w, 0, 1);
  2098. gtk_widget_show(w);
  2099. button = gtk_button_new_with_label("Up");
  2100. columns_add(COLUMNS(cols), button, 1, 1);
  2101. gtk_widget_show(button);
  2102. g_signal_connect(G_OBJECT(button), "clicked",
  2103. G_CALLBACK(draglist_up), dp);
  2104. g_signal_connect(G_OBJECT(button), "focus_in_event",
  2105. G_CALLBACK(widget_focus), dp);
  2106. button = gtk_button_new_with_label("Down");
  2107. columns_add(COLUMNS(cols), button, 1, 1);
  2108. gtk_widget_show(button);
  2109. g_signal_connect(G_OBJECT(button), "clicked",
  2110. G_CALLBACK(draglist_down), dp);
  2111. g_signal_connect(G_OBJECT(button), "focus_in_event",
  2112. G_CALLBACK(widget_focus), dp);
  2113. w = cols;
  2114. }
  2115. #else
  2116. /*
  2117. * GTK2 treeview-based full list box.
  2118. */
  2119. GtkTreeSelection *sel;
  2120. /*
  2121. * Create the list box itself, its columns, and
  2122. * its containing scrolled window.
  2123. */
  2124. w = gtk_tree_view_new_with_model(
  2125. GTK_TREE_MODEL(uc->listmodel));
  2126. g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
  2127. (gpointer)w);
  2128. gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
  2129. sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
  2130. gtk_tree_selection_set_mode(
  2131. sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
  2132. GTK_SELECTION_SINGLE);
  2133. uc->treeview = w;
  2134. g_signal_connect(G_OBJECT(w), "row-activated",
  2135. G_CALLBACK(listbox_doubleclick), dp);
  2136. g_signal_connect(G_OBJECT(w), "focus_in_event",
  2137. G_CALLBACK(widget_focus), dp);
  2138. g_signal_connect(G_OBJECT(sel), "changed",
  2139. G_CALLBACK(listbox_selchange), dp);
  2140. if (ctrl->listbox.draglist) {
  2141. gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true);
  2142. g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
  2143. G_CALLBACK(listbox_reorder), dp);
  2144. }
  2145. {
  2146. int i;
  2147. int cols;
  2148. cols = ctrl->listbox.ncols;
  2149. cols = cols ? cols : 1;
  2150. for (i = 0; i < cols; i++) {
  2151. GtkTreeViewColumn *column;
  2152. GtkCellRenderer *cellrend;
  2153. /*
  2154. * It appears that GTK 2 doesn't leave us any
  2155. * particularly sensible way to honour the
  2156. * "percentages" specification in the ctrl
  2157. * structure.
  2158. */
  2159. cellrend = gtk_cell_renderer_text_new();
  2160. if (!ctrl->listbox.hscroll) {
  2161. g_object_set(G_OBJECT(cellrend),
  2162. "ellipsize", PANGO_ELLIPSIZE_END,
  2163. "ellipsize-set", true,
  2164. (const char *)NULL);
  2165. }
  2166. column = gtk_tree_view_column_new_with_attributes(
  2167. "heading", cellrend, "text", i+1, (char *)NULL);
  2168. gtk_tree_view_column_set_sizing(
  2169. column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
  2170. gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
  2171. }
  2172. }
  2173. {
  2174. GtkWidget *scroll;
  2175. scroll = gtk_scrolled_window_new(NULL, NULL);
  2176. gtk_scrolled_window_set_shadow_type(
  2177. GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
  2178. gtk_widget_show(w);
  2179. gtk_container_add(GTK_CONTAINER(scroll), w);
  2180. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
  2181. GTK_POLICY_AUTOMATIC,
  2182. GTK_POLICY_ALWAYS);
  2183. gtk_widget_set_size_request(
  2184. scroll, -1,
  2185. ctrl->listbox.height * get_listitemheight(w));
  2186. w = scroll;
  2187. }
  2188. #endif
  2189. }
  2190. if (ctrl->label) {
  2191. GtkWidget *label, *container;
  2192. label = gtk_label_new(ctrl->label);
  2193. #if GTK_CHECK_VERSION(3,0,0)
  2194. gtk_label_set_width_chars(GTK_LABEL(label), 3);
  2195. #endif
  2196. shortcut_add(scs, label, ctrl->listbox.shortcut,
  2197. SHORTCUT_UCTRL, uc);
  2198. container = columns_new(4);
  2199. if (ctrl->listbox.percentwidth == 100) {
  2200. columns_add(COLUMNS(container), label, 0, 1);
  2201. columns_force_left_align(COLUMNS(container), label);
  2202. columns_add(COLUMNS(container), w, 0, 1);
  2203. } else {
  2204. gint percentages[2];
  2205. percentages[1] = ctrl->listbox.percentwidth;
  2206. percentages[0] = 100 - ctrl->listbox.percentwidth;
  2207. columns_set_cols(COLUMNS(container), 2, percentages);
  2208. columns_add(COLUMNS(container), label, 0, 1);
  2209. columns_force_left_align(COLUMNS(container), label);
  2210. columns_add(COLUMNS(container), w, 1, 1);
  2211. columns_align_next_to(COLUMNS(container), label, w);
  2212. }
  2213. gtk_widget_show(label);
  2214. gtk_widget_show(w);
  2215. w = container;
  2216. uc->label = label;
  2217. }
  2218. break;
  2219. case CTRL_TEXT:
  2220. #if !GTK_CHECK_VERSION(3,0,0)
  2221. /*
  2222. * Wrapping text widgets don't sit well with the GTK2
  2223. * layout model, in which widgets state a minimum size
  2224. * and the whole window then adjusts to the smallest
  2225. * size it can sensibly take given its contents. A
  2226. * wrapping text widget _has_ no clear minimum size;
  2227. * instead it has a range of possibilities. It can be
  2228. * one line deep but 2000 wide, or two lines deep and
  2229. * 1000 pixels, or three by 867, or four by 500 and so
  2230. * on. It can be as short as you like provided you
  2231. * don't mind it being wide, or as narrow as you like
  2232. * provided you don't mind it being tall.
  2233. *
  2234. * Therefore, it fits very badly into the layout model.
  2235. * Hence the only thing to do is pick a width and let
  2236. * it choose its own number of lines. To do this I'm
  2237. * going to cheat a little. All new wrapping text
  2238. * widgets will be created with a minimal text content
  2239. * "X"; then, after the rest of the dialog box is set
  2240. * up and its size calculated, the text widgets will be
  2241. * told their width and given their real text, which
  2242. * will cause the size to be recomputed in the y
  2243. * direction (because many of them will expand to more
  2244. * than one line).
  2245. */
  2246. uc->text = w = gtk_label_new("X");
  2247. uc->textsig =
  2248. g_signal_connect(G_OBJECT(w), "size-allocate",
  2249. G_CALLBACK(label_sizealloc), dp);
  2250. #else
  2251. /*
  2252. * In GTK3, this is all fixed, because the main aim of the
  2253. * new 'height-for-width' geometry management is to make
  2254. * wrapping labels behave sensibly. So now we can just do
  2255. * the obvious thing.
  2256. */
  2257. uc->text = w = gtk_label_new(uc->ctrl->label);
  2258. #endif
  2259. #if GTK_CHECK_VERSION(2,0,0)
  2260. gtk_label_set_selectable(GTK_LABEL(w), true);
  2261. gtk_widget_set_can_focus(w, false);
  2262. #endif
  2263. align_label_left(GTK_LABEL(w));
  2264. gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap);
  2265. if (!ctrl->text.wrap) {
  2266. gtk_widget_show(uc->text);
  2267. w = gtk_scrolled_window_new(NULL, NULL);
  2268. gtk_container_set_border_width(GTK_CONTAINER(w), 0);
  2269. gtk_container_add(GTK_CONTAINER(w), uc->text);
  2270. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
  2271. GTK_POLICY_AUTOMATIC,
  2272. GTK_POLICY_NEVER);
  2273. #if GTK_CHECK_VERSION(2,0,0)
  2274. gtk_widget_set_can_focus(w, false);
  2275. #endif
  2276. }
  2277. break;
  2278. }
  2279. assert(w != NULL);
  2280. columns_add(cols, w,
  2281. COLUMN_START(ctrl->column),
  2282. COLUMN_SPAN(ctrl->column));
  2283. if (left)
  2284. columns_force_left_align(cols, w);
  2285. if (ctrl->align_next_to) {
  2286. struct uctrl *uc2 = dlg_find_byctrl(
  2287. dp, ctrl->align_next_to);
  2288. assert(uc2);
  2289. columns_align_next_to(cols, w, uc2->toplevel);
  2290. #if GTK_CHECK_VERSION(3, 10, 0)
  2291. /* Slightly nicer to align baselines than just vertically
  2292. * centring, where the option is available */
  2293. gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
  2294. gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE);
  2295. #endif
  2296. }
  2297. gtk_widget_show(w);
  2298. uc->toplevel = w;
  2299. dlg_add_uctrl(dp, uc);
  2300. }
  2301. return ret;
  2302. }
  2303. struct selparam {
  2304. struct dlgparam *dp;
  2305. GtkNotebook *panels;
  2306. GtkWidget *panel;
  2307. #if !GTK_CHECK_VERSION(2,0,0)
  2308. GtkWidget *treeitem;
  2309. #else
  2310. int depth;
  2311. GtkTreePath *treepath;
  2312. #endif
  2313. struct Shortcuts shortcuts;
  2314. };
  2315. #if GTK_CHECK_VERSION(2,0,0)
  2316. static void treeselection_changed(GtkTreeSelection *treeselection,
  2317. gpointer data)
  2318. {
  2319. struct selparam **sps = (struct selparam **)data, *sp;
  2320. GtkTreeModel *treemodel;
  2321. GtkTreeIter treeiter;
  2322. gint spindex;
  2323. gint page_num;
  2324. if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
  2325. return;
  2326. gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
  2327. sp = sps[spindex];
  2328. page_num = gtk_notebook_page_num(sp->panels, sp->panel);
  2329. gtk_notebook_set_current_page(sp->panels, page_num);
  2330. sp->dp->curr_panel = sp;
  2331. dlg_refresh(NULL, sp->dp);
  2332. sp->dp->shortcuts = &sp->shortcuts;
  2333. }
  2334. #else
  2335. static void treeitem_sel(GtkItem *item, gpointer data)
  2336. {
  2337. struct selparam *sp = (struct selparam *)data;
  2338. gint page_num;
  2339. page_num = gtk_notebook_page_num(sp->panels, sp->panel);
  2340. gtk_notebook_set_page(sp->panels, page_num);
  2341. sp->dp->curr_panel = sp;
  2342. dlg_refresh(NULL, sp->dp);
  2343. sp->dp->shortcuts = &sp->shortcuts;
  2344. sp->dp->currtreeitem = sp->treeitem;
  2345. }
  2346. #endif
  2347. bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp)
  2348. {
  2349. struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
  2350. /*
  2351. * A control is visible if it belongs to _no_ notebook page (i.e.
  2352. * it's one of the config-box-global buttons like Load or About),
  2353. * or if it belongs to the currently selected page.
  2354. */
  2355. return uc->sp == NULL || uc->sp == dp->curr_panel;
  2356. }
  2357. #if !GTK_CHECK_VERSION(2,0,0)
  2358. static bool tree_grab_focus(struct dlgparam *dp)
  2359. {
  2360. int i, f;
  2361. /*
  2362. * See if any of the treeitems has the focus.
  2363. */
  2364. f = -1;
  2365. for (i = 0; i < dp->ntreeitems; i++)
  2366. if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
  2367. f = i;
  2368. break;
  2369. }
  2370. if (f >= 0)
  2371. return false;
  2372. else {
  2373. gtk_widget_grab_focus(dp->currtreeitem);
  2374. return true;
  2375. }
  2376. }
  2377. gint tree_focus(GtkContainer *container, GtkDirectionType direction,
  2378. gpointer data)
  2379. {
  2380. struct dlgparam *dp = (struct dlgparam *)data;
  2381. g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
  2382. /*
  2383. * If there's a focused treeitem, we return false to cause the
  2384. * focus to move on to some totally other control. If not, we
  2385. * focus the selected one.
  2386. */
  2387. return tree_grab_focus(dp);
  2388. }
  2389. #endif
  2390. gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
  2391. {
  2392. struct dlgparam *dp = (struct dlgparam *)data;
  2393. if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
  2394. g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
  2395. return true;
  2396. }
  2397. if ((event->state & GDK_MOD1_MASK) &&
  2398. (unsigned char)event->string[0] > 0 &&
  2399. (unsigned char)event->string[0] <= 127) {
  2400. int schr = (unsigned char)event->string[0];
  2401. struct Shortcut *sc = &dp->shortcuts->sc[schr];
  2402. switch (sc->action) {
  2403. case SHORTCUT_TREE:
  2404. #if GTK_CHECK_VERSION(2,0,0)
  2405. gtk_widget_grab_focus(sc->widget);
  2406. #else
  2407. tree_grab_focus(dp);
  2408. #endif
  2409. break;
  2410. case SHORTCUT_FOCUS:
  2411. gtk_widget_grab_focus(sc->widget);
  2412. break;
  2413. case SHORTCUT_UCTRL:
  2414. /*
  2415. * We must do something sensible with a uctrl.
  2416. * Precisely what this is depends on the type of
  2417. * control.
  2418. */
  2419. switch (sc->uc->ctrl->type) {
  2420. case CTRL_CHECKBOX:
  2421. case CTRL_BUTTON:
  2422. /* Check boxes and buttons get the focus _and_ get toggled. */
  2423. gtk_widget_grab_focus(sc->uc->toplevel);
  2424. g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
  2425. break;
  2426. case CTRL_FILESELECT:
  2427. case CTRL_FONTSELECT:
  2428. /* File/font selectors have their buttons pressed (ooer),
  2429. * and focus transferred to the edit box. */
  2430. g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
  2431. if (sc->uc->entry)
  2432. gtk_widget_grab_focus(sc->uc->entry);
  2433. break;
  2434. case CTRL_RADIO:
  2435. /*
  2436. * Radio buttons are fun, because they have
  2437. * multiple shortcuts. We must find whether the
  2438. * activated shortcut is the shortcut for the whole
  2439. * group, or for a particular button. In the former
  2440. * case, we find the currently selected button and
  2441. * focus it; in the latter, we focus-and-click the
  2442. * button whose shortcut was pressed.
  2443. */
  2444. if (schr == sc->uc->ctrl->radio.shortcut) {
  2445. int i;
  2446. for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
  2447. if (gtk_toggle_button_get_active(
  2448. GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
  2449. gtk_widget_grab_focus(sc->uc->buttons[i]);
  2450. }
  2451. } else if (sc->uc->ctrl->radio.shortcuts) {
  2452. int i;
  2453. for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
  2454. if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
  2455. gtk_widget_grab_focus(sc->uc->buttons[i]);
  2456. g_signal_emit_by_name(
  2457. G_OBJECT(sc->uc->buttons[i]), "clicked");
  2458. }
  2459. }
  2460. break;
  2461. case CTRL_LISTBOX:
  2462. #if !GTK_CHECK_VERSION(2,4,0)
  2463. if (sc->uc->optmenu) {
  2464. GdkEventButton bev;
  2465. gint returnval;
  2466. gtk_widget_grab_focus(sc->uc->optmenu);
  2467. /* Option menus don't work using the "clicked" signal.
  2468. * We need to manufacture a button press event :-/ */
  2469. bev.type = GDK_BUTTON_PRESS;
  2470. bev.button = 1;
  2471. g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
  2472. "button_press_event",
  2473. &bev, &returnval);
  2474. break;
  2475. }
  2476. #else
  2477. if (sc->uc->combo) {
  2478. gtk_widget_grab_focus(sc->uc->combo);
  2479. gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
  2480. break;
  2481. }
  2482. #endif
  2483. #if !GTK_CHECK_VERSION(2,0,0)
  2484. if (sc->uc->list) {
  2485. /*
  2486. * For GTK-1 style list boxes, we tell it to
  2487. * focus one of its children, which appears to
  2488. * do the Right Thing.
  2489. */
  2490. gtk_container_focus(GTK_CONTAINER(sc->uc->list),
  2491. GTK_DIR_TAB_FORWARD);
  2492. break;
  2493. }
  2494. #else
  2495. if (sc->uc->treeview) {
  2496. gtk_widget_grab_focus(sc->uc->treeview);
  2497. break;
  2498. }
  2499. #endif
  2500. unreachable("bad listbox type in win_key_press");
  2501. }
  2502. break;
  2503. }
  2504. }
  2505. return false;
  2506. }
  2507. #if !GTK_CHECK_VERSION(2,0,0)
  2508. gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
  2509. {
  2510. struct dlgparam *dp = (struct dlgparam *)data;
  2511. if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
  2512. event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
  2513. int dir, i, j = -1;
  2514. for (i = 0; i < dp->ntreeitems; i++)
  2515. if (widget == dp->treeitems[i])
  2516. break;
  2517. if (i < dp->ntreeitems) {
  2518. if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
  2519. dir = -1;
  2520. else
  2521. dir = +1;
  2522. while (1) {
  2523. i += dir;
  2524. if (i < 0 || i >= dp->ntreeitems)
  2525. break; /* nothing in that dir to select */
  2526. /*
  2527. * Determine if this tree item is visible.
  2528. */
  2529. {
  2530. GtkWidget *w = dp->treeitems[i];
  2531. bool vis = true;
  2532. while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
  2533. if (!GTK_WIDGET_VISIBLE(w)) {
  2534. vis = false;
  2535. break;
  2536. }
  2537. w = w->parent;
  2538. }
  2539. if (vis) {
  2540. j = i; /* got one */
  2541. break;
  2542. }
  2543. }
  2544. }
  2545. }
  2546. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2547. if (j >= 0) {
  2548. g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
  2549. gtk_widget_grab_focus(dp->treeitems[j]);
  2550. }
  2551. return true;
  2552. }
  2553. /*
  2554. * It's nice for Left and Right to expand and collapse tree
  2555. * branches.
  2556. */
  2557. if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
  2558. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2559. gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
  2560. return true;
  2561. }
  2562. if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
  2563. g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
  2564. gtk_tree_item_expand(GTK_TREE_ITEM(widget));
  2565. return true;
  2566. }
  2567. return false;
  2568. }
  2569. #endif
  2570. static void shortcut_highlight(GtkWidget *labelw, int chr)
  2571. {
  2572. GtkLabel *label = GTK_LABEL(labelw);
  2573. const gchar *currstr;
  2574. gchar *pattern;
  2575. int i;
  2576. #if !GTK_CHECK_VERSION(2,0,0)
  2577. {
  2578. gchar *currstr_nonconst;
  2579. gtk_label_get(label, &currstr_nonconst);
  2580. currstr = currstr_nonconst;
  2581. }
  2582. #else
  2583. currstr = gtk_label_get_text(label);
  2584. #endif
  2585. for (i = 0; currstr[i]; i++)
  2586. if (tolower((unsigned char)currstr[i]) == chr) {
  2587. pattern = dupprintf("%*s_", i, "");
  2588. gtk_label_set_pattern(label, pattern);
  2589. sfree(pattern);
  2590. break;
  2591. }
  2592. }
  2593. void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
  2594. int chr, int action, void *ptr)
  2595. {
  2596. if (chr == NO_SHORTCUT)
  2597. return;
  2598. chr = tolower((unsigned char)chr);
  2599. assert(scs->sc[chr].action == SHORTCUT_EMPTY);
  2600. scs->sc[chr].action = action;
  2601. if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
  2602. scs->sc[chr].uc = NULL;
  2603. scs->sc[chr].widget = (GtkWidget *)ptr;
  2604. } else {
  2605. scs->sc[chr].widget = NULL;
  2606. scs->sc[chr].uc = (struct uctrl *)ptr;
  2607. }
  2608. shortcut_highlight(labelw, chr);
  2609. }
  2610. static int get_listitemheight(GtkWidget *w)
  2611. {
  2612. #if !GTK_CHECK_VERSION(2,0,0)
  2613. GtkWidget *listitem = gtk_list_item_new_with_label("foo");
  2614. GtkRequisition req;
  2615. gtk_widget_size_request(listitem, &req);
  2616. g_object_ref_sink(G_OBJECT(listitem));
  2617. return req.height;
  2618. #else
  2619. int height;
  2620. GtkCellRenderer *cr = gtk_cell_renderer_text_new();
  2621. #if GTK_CHECK_VERSION(3,0,0)
  2622. {
  2623. GtkRequisition req;
  2624. /*
  2625. * Since none of my list items wraps in this GUI, no
  2626. * interesting width-for-height behaviour should be happening,
  2627. * so I don't think it should matter here whether I ask for
  2628. * the minimum or natural height.
  2629. */
  2630. gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
  2631. height = req.height;
  2632. }
  2633. #else
  2634. gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
  2635. #endif
  2636. g_object_ref(G_OBJECT(cr));
  2637. g_object_ref_sink(G_OBJECT(cr));
  2638. g_object_unref(G_OBJECT(cr));
  2639. return height;
  2640. #endif
  2641. }
  2642. #if GTK_CHECK_VERSION(2,0,0)
  2643. void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
  2644. {
  2645. /*
  2646. * Collapse the deeper branches of the treeview into the state we
  2647. * like them to start off in. See comment below in do_config_box.
  2648. */
  2649. int i;
  2650. for (i = 0; i < dp->nselparams; i++)
  2651. if (dp->selparams[i]->depth >= 2)
  2652. gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
  2653. dp->selparams[i]->treepath);
  2654. }
  2655. #endif
  2656. #if GTK_CHECK_VERSION(3,0,0)
  2657. void treeview_map_event(GtkWidget *tree, gpointer data)
  2658. {
  2659. struct dlgparam *dp = (struct dlgparam *)data;
  2660. GtkAllocation alloc;
  2661. gtk_widget_get_allocation(tree, &alloc);
  2662. gtk_widget_set_size_request(tree, alloc.width, -1);
  2663. initial_treeview_collapse(dp, tree);
  2664. }
  2665. #endif
  2666. GtkWidget *create_config_box(const char *title, Conf *conf,
  2667. bool midsession, int protcfginfo,
  2668. post_dialog_fn_t after, void *afterctx)
  2669. {
  2670. GtkWidget *window, *hbox, *vbox, *cols, *label,
  2671. *tree, *treescroll, *panels, *panelvbox;
  2672. int index, level, protocol;
  2673. char *path;
  2674. #if GTK_CHECK_VERSION(2,0,0)
  2675. GtkTreeStore *treestore;
  2676. GtkCellRenderer *treerenderer;
  2677. GtkTreeViewColumn *treecolumn;
  2678. GtkTreeSelection *treeselection;
  2679. GtkTreeIter treeiterlevels[8];
  2680. #else
  2681. GtkTreeItem *treeitemlevels[8];
  2682. GtkTree *treelevels[8];
  2683. #endif
  2684. struct dlgparam *dp;
  2685. struct Shortcuts scs;
  2686. struct selparam **selparams = NULL;
  2687. size_t nselparams = 0, selparamsize = 0;
  2688. dp = snew(struct dlgparam);
  2689. dp->after = after;
  2690. dp->afterctx = afterctx;
  2691. dlg_init(dp);
  2692. for (index = 0; index < lenof(scs.sc); index++) {
  2693. scs.sc[index].action = SHORTCUT_EMPTY;
  2694. }
  2695. window = our_dialog_new();
  2696. dp->ctrlbox = ctrl_new_box();
  2697. protocol = conf_get_int(conf, CONF_protocol);
  2698. setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo);
  2699. unix_setup_config_box(dp->ctrlbox, midsession, protocol);
  2700. gtk_setup_config_box(dp->ctrlbox, midsession, window);
  2701. gtk_window_set_title(GTK_WINDOW(window), title);
  2702. hbox = gtk_hbox_new(false, 4);
  2703. our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, true, true, 0);
  2704. gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
  2705. gtk_widget_show(hbox);
  2706. vbox = gtk_vbox_new(false, 4);
  2707. gtk_box_pack_start(GTK_BOX(hbox), vbox, false, false, 0);
  2708. gtk_widget_show(vbox);
  2709. cols = columns_new(4);
  2710. gtk_box_pack_start(GTK_BOX(vbox), cols, false, false, 0);
  2711. gtk_widget_show(cols);
  2712. label = gtk_label_new("Category:");
  2713. columns_add(COLUMNS(cols), label, 0, 1);
  2714. columns_force_left_align(COLUMNS(cols), label);
  2715. gtk_widget_show(label);
  2716. treescroll = gtk_scrolled_window_new(NULL, NULL);
  2717. #if GTK_CHECK_VERSION(2,0,0)
  2718. treestore = gtk_tree_store_new(
  2719. TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
  2720. tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
  2721. gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false);
  2722. treerenderer = gtk_cell_renderer_text_new();
  2723. treecolumn = gtk_tree_view_column_new_with_attributes(
  2724. "Label", treerenderer, "text", 0, NULL);
  2725. gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
  2726. treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  2727. gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
  2728. gtk_container_add(GTK_CONTAINER(treescroll), tree);
  2729. #else
  2730. tree = gtk_tree_new();
  2731. gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
  2732. gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
  2733. g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp);
  2734. #endif
  2735. g_signal_connect(G_OBJECT(tree), "focus_in_event",
  2736. G_CALLBACK(widget_focus), dp);
  2737. shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
  2738. gtk_widget_show(treescroll);
  2739. gtk_box_pack_start(GTK_BOX(vbox), treescroll, true, true, 0);
  2740. panels = gtk_notebook_new();
  2741. gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false);
  2742. gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false);
  2743. gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0);
  2744. gtk_widget_show(panels);
  2745. panelvbox = NULL;
  2746. path = NULL;
  2747. level = 0;
  2748. for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
  2749. struct controlset *s = dp->ctrlbox->ctrlsets[index];
  2750. GtkWidget *w;
  2751. if (!*s->pathname) {
  2752. w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window));
  2753. our_dialog_set_action_area(GTK_WINDOW(window), w);
  2754. } else {
  2755. int j = path ? ctrl_path_compare(s->pathname, path) : 0;
  2756. if (j != INT_MAX) { /* add to treeview, start new panel */
  2757. char *c;
  2758. #if GTK_CHECK_VERSION(2,0,0)
  2759. GtkTreeIter treeiter;
  2760. #else
  2761. GtkWidget *treeitem;
  2762. #endif
  2763. bool first;
  2764. /*
  2765. * We expect never to find an implicit path
  2766. * component. For example, we expect never to see
  2767. * A/B/C followed by A/D/E, because that would
  2768. * _implicitly_ create A/D. All our path prefixes
  2769. * are expected to contain actual controls and be
  2770. * selectable in the treeview; so we would expect
  2771. * to see A/D _explicitly_ before encountering
  2772. * A/D/E.
  2773. */
  2774. assert(j == ctrl_path_elements(s->pathname) - 1);
  2775. c = strrchr(s->pathname, '/');
  2776. if (!c)
  2777. c = s->pathname;
  2778. else
  2779. c++;
  2780. path = s->pathname;
  2781. first = (panelvbox == NULL);
  2782. panelvbox = gtk_vbox_new(false, 4);
  2783. gtk_widget_show(panelvbox);
  2784. gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
  2785. NULL);
  2786. struct selparam *sp = snew(struct selparam);
  2787. if (first) {
  2788. gint page_num;
  2789. page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
  2790. panelvbox);
  2791. gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
  2792. page_num);
  2793. dp->curr_panel = sp;
  2794. }
  2795. sgrowarray(selparams, selparamsize, nselparams);
  2796. selparams[nselparams] = sp;
  2797. sp->dp = dp;
  2798. sp->panels = GTK_NOTEBOOK(panels);
  2799. sp->panel = panelvbox;
  2800. sp->shortcuts = scs; /* structure copy */
  2801. assert(j-1 < level);
  2802. #if GTK_CHECK_VERSION(2,0,0)
  2803. if (j > 0)
  2804. /* treeiterlevels[j-1] will always be valid because we
  2805. * don't allow implicit path components; see above.
  2806. */
  2807. gtk_tree_store_append(treestore, &treeiter,
  2808. &treeiterlevels[j-1]);
  2809. else
  2810. gtk_tree_store_append(treestore, &treeiter, NULL);
  2811. gtk_tree_store_set(treestore, &treeiter,
  2812. TREESTORE_PATH, c,
  2813. TREESTORE_PARAMS, nselparams,
  2814. -1);
  2815. treeiterlevels[j] = treeiter;
  2816. sp->depth = j;
  2817. if (j > 0) {
  2818. sp->treepath = gtk_tree_model_get_path(
  2819. GTK_TREE_MODEL(treestore), &treeiterlevels[j-1]);
  2820. /*
  2821. * We are going to collapse all tree branches
  2822. * at depth greater than 2, but not _yet_; see
  2823. * the comment at the call to
  2824. * gtk_tree_view_collapse_row below.
  2825. */
  2826. gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
  2827. sp->treepath, false);
  2828. } else {
  2829. sp->treepath = NULL;
  2830. }
  2831. #else
  2832. treeitem = gtk_tree_item_new_with_label(c);
  2833. if (j > 0) {
  2834. if (!treelevels[j-1]) {
  2835. treelevels[j-1] = GTK_TREE(gtk_tree_new());
  2836. gtk_tree_item_set_subtree(
  2837. treeitemlevels[j-1], GTK_WIDGET(treelevels[j-1]));
  2838. if (j < 2)
  2839. gtk_tree_item_expand(treeitemlevels[j-1]);
  2840. else
  2841. gtk_tree_item_collapse(treeitemlevels[j-1]);
  2842. }
  2843. gtk_tree_append(treelevels[j-1], treeitem);
  2844. } else {
  2845. gtk_tree_append(GTK_TREE(tree), treeitem);
  2846. }
  2847. treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
  2848. treelevels[j] = NULL;
  2849. g_signal_connect(G_OBJECT(treeitem), "key_press_event",
  2850. G_CALLBACK(tree_key_press), dp);
  2851. g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
  2852. G_CALLBACK(widget_focus), dp);
  2853. gtk_widget_show(treeitem);
  2854. if (first)
  2855. gtk_tree_select_child(GTK_TREE(tree), treeitem);
  2856. sp->treeitem = treeitem;
  2857. #endif
  2858. level = j+1;
  2859. nselparams++;
  2860. }
  2861. w = layout_ctrls(dp, selparams[nselparams-1],
  2862. &selparams[nselparams-1]->shortcuts, s, NULL);
  2863. gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0);
  2864. gtk_widget_show(w);
  2865. }
  2866. }
  2867. #if GTK_CHECK_VERSION(2,0,0)
  2868. /*
  2869. * We want our tree view to come up with all branches at depth 2
  2870. * or more collapsed. However, if we start off with those branches
  2871. * collapsed, then the tree view's size request will be calculated
  2872. * based on the width of the collapsed tree, and then when the
  2873. * collapsed branches are expanded later, the tree view will
  2874. * jarringly change size.
  2875. *
  2876. * So instead we start with everything expanded; then, once the
  2877. * tree view has computed its resulting width requirement, we
  2878. * collapse the relevant rows, but force the width to be the value
  2879. * we just retrieved. This arranges that the tree view is wide
  2880. * enough to have all branches expanded without further resizing.
  2881. */
  2882. dp->nselparams = nselparams;
  2883. dp->selparams = selparams;
  2884. #if !GTK_CHECK_VERSION(3,0,0)
  2885. {
  2886. /*
  2887. * In GTK2, we can just do the job right now.
  2888. */
  2889. GtkRequisition req;
  2890. gtk_widget_size_request(tree, &req);
  2891. initial_treeview_collapse(dp, tree);
  2892. gtk_widget_set_size_request(tree, req.width, -1);
  2893. }
  2894. #else
  2895. /*
  2896. * But in GTK3, we have to wait until the widget is about to be
  2897. * mapped, because the size computation won't have been done yet.
  2898. */
  2899. g_signal_connect(G_OBJECT(tree), "map",
  2900. G_CALLBACK(treeview_map_event), dp);
  2901. #endif /* GTK 2 vs 3 */
  2902. #endif /* GTK 2+ vs 1 */
  2903. #if GTK_CHECK_VERSION(2,0,0)
  2904. g_signal_connect(G_OBJECT(treeselection), "changed",
  2905. G_CALLBACK(treeselection_changed), selparams);
  2906. #else
  2907. dp->ntreeitems = nselparams;
  2908. dp->treeitems = snewn(dp->ntreeitems, GtkWidget *);
  2909. for (index = 0; index < nselparams; index++) {
  2910. g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select",
  2911. G_CALLBACK(treeitem_sel),
  2912. selparams[index]);
  2913. dp->treeitems[index] = selparams[index]->treeitem;
  2914. }
  2915. #endif
  2916. dp->data = conf;
  2917. dlg_refresh(NULL, dp);
  2918. dp->shortcuts = &selparams[0]->shortcuts;
  2919. #if !GTK_CHECK_VERSION(2,0,0)
  2920. dp->currtreeitem = dp->treeitems[0];
  2921. #endif
  2922. dp->lastfocus = NULL;
  2923. dp->retval = -1;
  2924. dp->window = window;
  2925. set_window_icon(window, cfg_icon, n_cfg_icon);
  2926. #if !GTK_CHECK_VERSION(2,0,0)
  2927. gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
  2928. tree);
  2929. #endif
  2930. gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
  2931. GTK_POLICY_NEVER,
  2932. GTK_POLICY_AUTOMATIC);
  2933. gtk_widget_show(tree);
  2934. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  2935. gtk_widget_show(window);
  2936. /*
  2937. * Set focus into the first available control.
  2938. */
  2939. for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
  2940. struct controlset *s = dp->ctrlbox->ctrlsets[index];
  2941. bool done = false;
  2942. int j;
  2943. if (*s->pathname) {
  2944. for (j = 0; j < s->ncontrols; j++)
  2945. if (s->ctrls[j]->type != CTRL_TABDELAY &&
  2946. s->ctrls[j]->type != CTRL_COLUMNS &&
  2947. s->ctrls[j]->type != CTRL_TEXT) {
  2948. dlg_set_focus(s->ctrls[j], dp);
  2949. dp->lastfocus = s->ctrls[j];
  2950. done = true;
  2951. break;
  2952. }
  2953. }
  2954. if (done)
  2955. break;
  2956. }
  2957. g_signal_connect(G_OBJECT(window), "destroy",
  2958. G_CALLBACK(dlgparam_destroy), dp);
  2959. g_signal_connect(G_OBJECT(window), "key_press_event",
  2960. G_CALLBACK(win_key_press), dp);
  2961. return window;
  2962. }
  2963. static void dlgparam_destroy(GtkWidget *widget, gpointer data)
  2964. {
  2965. struct dlgparam *dp = (struct dlgparam *)data;
  2966. dp->after(dp->afterctx, dp->retval);
  2967. dlg_cleanup(dp);
  2968. ctrl_free_box(dp->ctrlbox);
  2969. #if GTK_CHECK_VERSION(2,0,0)
  2970. if (dp->selparams) {
  2971. for (size_t i = 0; i < dp->nselparams; i++) {
  2972. if (dp->selparams[i]->treepath)
  2973. gtk_tree_path_free(dp->selparams[i]->treepath);
  2974. sfree(dp->selparams[i]);
  2975. }
  2976. sfree(dp->selparams);
  2977. }
  2978. #endif
  2979. sfree(dp);
  2980. }
  2981. static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp,
  2982. void *data, int event)
  2983. {
  2984. if (event == EVENT_ACTION)
  2985. dlg_end(dp, ctrl->context.i);
  2986. }
  2987. static const struct message_box_button button_array_yn[] = {
  2988. {"Yes", 'y', +1, 1},
  2989. {"No", 'n', -1, 0},
  2990. };
  2991. const struct message_box_buttons buttons_yn = {
  2992. button_array_yn, lenof(button_array_yn),
  2993. };
  2994. static const struct message_box_button button_array_ok[] = {
  2995. {"OK", 'o', 1, 1},
  2996. };
  2997. const struct message_box_buttons buttons_ok = {
  2998. button_array_ok, lenof(button_array_ok),
  2999. };
  3000. static GtkWidget *create_message_box_general(
  3001. GtkWidget *parentwin, const char *title, const char *msg, int minwid,
  3002. bool selectable, const struct message_box_buttons *buttons,
  3003. post_dialog_fn_t after, void *afterctx,
  3004. GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx)
  3005. {
  3006. GtkWidget *window, *w0, *w1;
  3007. struct controlset *s0, *s1;
  3008. dlgcontrol *c, *textctrl;
  3009. struct dlgparam *dp;
  3010. struct Shortcuts scs;
  3011. int i, index, ncols, min_type;
  3012. dp = snew(struct dlgparam);
  3013. dp->after = after;
  3014. dp->afterctx = afterctx;
  3015. dlg_init(dp);
  3016. for (index = 0; index < lenof(scs.sc); index++) {
  3017. scs.sc[index].action = SHORTCUT_EMPTY;
  3018. }
  3019. dp->ctrlbox = ctrl_new_box();
  3020. /*
  3021. * Count up the number of buttons and find out what kinds there
  3022. * are.
  3023. */
  3024. ncols = 0;
  3025. min_type = +1;
  3026. for (i = 0; i < buttons->nbuttons; i++) {
  3027. const struct message_box_button *button = &buttons->buttons[i];
  3028. ncols++;
  3029. if (min_type > button->type)
  3030. min_type = button->type;
  3031. assert(button->value >= 0); /* <0 means no return value available */
  3032. }
  3033. s0 = ctrl_getset(dp->ctrlbox, "", "", "");
  3034. c = ctrl_columns(s0, 2, 50, 50);
  3035. c->columns.ncols = s0->ncolumns = ncols;
  3036. c->columns.percentages = sresize(c->columns.percentages, ncols, int);
  3037. for (index = 0; index < ncols; index++)
  3038. c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
  3039. index = 0;
  3040. for (i = 0; i < buttons->nbuttons; i++) {
  3041. const struct message_box_button *button = &buttons->buttons[i];
  3042. c = ctrl_pushbutton(s0, button->title, button->shortcut,
  3043. HELPCTX(no_help), messagebox_handler,
  3044. I(button->value));
  3045. c->column = index++;
  3046. if (button->type > 0)
  3047. c->button.isdefault = true;
  3048. /* We always arrange that _some_ button is labelled as
  3049. * 'iscancel', so that pressing Escape will always cause
  3050. * win_key_press to do something. The button we choose is
  3051. * whichever has the smallest type value: this means that real
  3052. * cancel buttons (labelled -1) will be picked if one is
  3053. * there, or in cases where the options are yes/no (1,0) then
  3054. * no will be picked, and if there's only one option (a box
  3055. * that really is just showing a _message_ and not even asking
  3056. * a question) then that will be picked. */
  3057. if (button->type == min_type)
  3058. c->button.iscancel = true;
  3059. }
  3060. s1 = ctrl_getset(dp->ctrlbox, "x", "", "");
  3061. textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
  3062. window = our_dialog_new();
  3063. gtk_window_set_title(GTK_WINDOW(window), title);
  3064. w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window));
  3065. if (action_postproc)
  3066. w0 = action_postproc(w0, postproc_ctx);
  3067. our_dialog_set_action_area(GTK_WINDOW(window), w0);
  3068. gtk_widget_show(w0);
  3069. w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window));
  3070. gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
  3071. gtk_widget_set_size_request(w1, minwid+20, -1);
  3072. our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
  3073. gtk_widget_show(w1);
  3074. dp->shortcuts = &scs;
  3075. dp->lastfocus = NULL;
  3076. dp->retval = 0;
  3077. dp->window = window;
  3078. if (parentwin) {
  3079. set_transient_window_pos(parentwin, window);
  3080. gtk_window_set_transient_for(GTK_WINDOW(window),
  3081. GTK_WINDOW(parentwin));
  3082. } else
  3083. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  3084. gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
  3085. gtk_widget_show(window);
  3086. gtk_window_set_focus(GTK_WINDOW(window), NULL);
  3087. #if GTK_CHECK_VERSION(2,0,0)
  3088. if (selectable) {
  3089. /*
  3090. * GTK selectable labels have a habit of selecting their
  3091. * entire contents when they gain focus. As far as I can see,
  3092. * an individual GtkLabel has no way to turn this off - source
  3093. * diving suggests that the only configurable option for it is
  3094. * "gtk-label-select-on-focus" in the cross-application
  3095. * GtkSettings, and there's no per-label or even
  3096. * per-application override.
  3097. *
  3098. * It's ugly to have text in a message box start up all
  3099. * selected, and also it interferes with any PRIMARY selection
  3100. * you might already have had. So for this purpose we'd prefer
  3101. * that the text doesn't _start off_ selected, but it should
  3102. * be selectable later.
  3103. *
  3104. * So we make the label selectable _now_, after the widget is
  3105. * shown and the focus has already gone wherever it's going.
  3106. */
  3107. struct uctrl *uc = dlg_find_byctrl(dp, textctrl);
  3108. gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
  3109. gtk_label_set_selectable(GTK_LABEL(uc->text), true);
  3110. }
  3111. #else
  3112. (void)textctrl; /* placate warning */
  3113. #endif
  3114. #if !GTK_CHECK_VERSION(2,0,0)
  3115. dp->currtreeitem = NULL;
  3116. dp->treeitems = NULL;
  3117. #else
  3118. dp->selparams = NULL;
  3119. #endif
  3120. g_signal_connect(G_OBJECT(window), "destroy",
  3121. G_CALLBACK(dlgparam_destroy), dp);
  3122. g_signal_connect(G_OBJECT(window), "key_press_event",
  3123. G_CALLBACK(win_key_press), dp);
  3124. return window;
  3125. }
  3126. GtkWidget *create_message_box(
  3127. GtkWidget *parentwin, const char *title, const char *msg, int minwid,
  3128. bool selectable, const struct message_box_buttons *buttons,
  3129. post_dialog_fn_t after, void *afterctx)
  3130. {
  3131. return create_message_box_general(
  3132. parentwin, title, msg, minwid, selectable, buttons, after, afterctx,
  3133. NULL /* action_postproc */, NULL /* postproc_ctx */);
  3134. }
  3135. struct confirm_ssh_host_key_dialog_ctx {
  3136. char *host;
  3137. int port;
  3138. char *keytype;
  3139. char *keystr;
  3140. char *more_info;
  3141. void (*callback)(void *callback_ctx, SeatPromptResult result);
  3142. void *callback_ctx;
  3143. Seat *seat;
  3144. GtkWidget *main_dialog;
  3145. GtkWidget *more_info_dialog;
  3146. };
  3147. static void confirm_ssh_host_key_result_callback(void *vctx, int result)
  3148. {
  3149. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3150. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3151. if (result >= 0) {
  3152. SeatPromptResult logical_result;
  3153. /*
  3154. * Convert the dialog-box return value (one of three
  3155. * possibilities) into the return value we pass back to the SSH
  3156. * code (one of only two possibilities, because the SSH code
  3157. * doesn't care whether we saved the host key or not).
  3158. */
  3159. if (result == 2) {
  3160. store_host_key(ctx->seat, ctx->host, ctx->port,
  3161. ctx->keytype, ctx->keystr);
  3162. logical_result = SPR_OK;
  3163. } else if (result == 1) {
  3164. logical_result = SPR_OK;
  3165. } else {
  3166. logical_result = SPR_USER_ABORT;
  3167. }
  3168. ctx->callback(ctx->callback_ctx, logical_result);
  3169. }
  3170. /*
  3171. * Clean up this context structure, whether or not a result was
  3172. * ever actually delivered from the dialog box.
  3173. */
  3174. unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT);
  3175. if (ctx->more_info_dialog)
  3176. gtk_widget_destroy(ctx->more_info_dialog);
  3177. sfree(ctx->host);
  3178. sfree(ctx->keytype);
  3179. sfree(ctx->keystr);
  3180. sfree(ctx->more_info);
  3181. sfree(ctx);
  3182. }
  3183. static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx)
  3184. {
  3185. GtkWidget *box = gtk_hbox_new(false, 10);
  3186. gtk_widget_show(box);
  3187. gtk_box_pack_end(GTK_BOX(box), w, false, true, 0);
  3188. GtkWidget *button = gtk_button_new_with_label("More info...");
  3189. gtk_widget_show(button);
  3190. gtk_box_pack_start(GTK_BOX(box), button, false, true, 0);
  3191. *(GtkWidget **)vctx = button;
  3192. return box;
  3193. }
  3194. static void more_info_closed(void *vctx, int result)
  3195. {
  3196. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3197. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3198. ctx->more_info_dialog = NULL;
  3199. }
  3200. static void more_info_button_clicked(GtkButton *button, gpointer vctx)
  3201. {
  3202. struct confirm_ssh_host_key_dialog_ctx *ctx =
  3203. (struct confirm_ssh_host_key_dialog_ctx *)vctx;
  3204. if (ctx->more_info_dialog)
  3205. return;
  3206. ctx->more_info_dialog = create_message_box(
  3207. ctx->main_dialog, "Host key information", ctx->more_info,
  3208. string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 "
  3209. "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true,
  3210. &buttons_ok, more_info_closed, ctx);
  3211. }
  3212. const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat)
  3213. {
  3214. static const SeatDialogPromptDescriptions descs = {
  3215. .hk_accept_action = "press \"Accept\"",
  3216. .hk_connect_once_action = "press \"Connect Once\"",
  3217. .hk_cancel_action = "press \"Cancel\"",
  3218. .hk_cancel_action_Participle = "Pressing \"Cancel\"",
  3219. .weak_accept_action = "press \"Yes\"",
  3220. .weak_cancel_action = "press \"No\"",
  3221. };
  3222. return &descs;
  3223. }
  3224. /*
  3225. * Format a SeatDialogText into a strbuf, also adjusting the box width
  3226. * to cope with displayed text. Returns the dialog box title.
  3227. */
  3228. static const char *gtk_format_seatdialogtext(
  3229. SeatDialogText *text, strbuf *dlg_text, int *width)
  3230. {
  3231. const char *dlg_title = NULL;
  3232. for (SeatDialogTextItem *item = text->items,
  3233. *end = item + text->nitems; item < end; item++) {
  3234. switch (item->type) {
  3235. case SDT_PARA:
  3236. put_fmt(dlg_text, "%s\n\n", item->text);
  3237. break;
  3238. case SDT_DISPLAY: {
  3239. put_fmt(dlg_text, "%s\n\n", item->text);
  3240. int thiswidth = string_width(item->text);
  3241. if (*width < thiswidth)
  3242. *width = thiswidth;
  3243. break;
  3244. }
  3245. case SDT_SCARY_HEADING:
  3246. /* Can't change font size or weight in this context */
  3247. put_fmt(dlg_text, "%s\n\n", item->text);
  3248. break;
  3249. case SDT_TITLE:
  3250. dlg_title = item->text;
  3251. break;
  3252. default:
  3253. break;
  3254. }
  3255. }
  3256. /*
  3257. * Trim trailing newlines.
  3258. */
  3259. while (strbuf_chomp(dlg_text, '\n'));
  3260. return dlg_title;
  3261. }
  3262. SeatPromptResult gtk_seat_confirm_ssh_host_key(
  3263. Seat *seat, const char *host, int port, const char *keytype,
  3264. char *keystr, SeatDialogText *text, HelpCtx helpctx,
  3265. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3266. {
  3267. static const struct message_box_button button_array_hostkey[] = {
  3268. {"Accept", 'a', 0, 2},
  3269. {"Connect Once", 'o', 0, 1},
  3270. {"Cancel", 'c', -1, 0},
  3271. };
  3272. static const struct message_box_buttons buttons_hostkey = {
  3273. button_array_hostkey, lenof(button_array_hostkey),
  3274. };
  3275. int width = string_width("default dialog width determination string");
  3276. strbuf *dlg_text = strbuf_new();
  3277. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3278. GtkWidget *mainwin, *msgbox;
  3279. struct confirm_ssh_host_key_dialog_ctx *result_ctx =
  3280. snew(struct confirm_ssh_host_key_dialog_ctx);
  3281. result_ctx->callback = callback;
  3282. result_ctx->callback_ctx = ctx;
  3283. result_ctx->host = dupstr(host);
  3284. result_ctx->port = port;
  3285. result_ctx->keytype = dupstr(keytype);
  3286. result_ctx->keystr = dupstr(keystr);
  3287. result_ctx->seat = seat;
  3288. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3289. GtkWidget *more_info_button = NULL;
  3290. msgbox = create_message_box_general(
  3291. mainwin, dlg_title, dlg_text->s, width, true,
  3292. &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx,
  3293. add_more_info_button, &more_info_button);
  3294. result_ctx->main_dialog = msgbox;
  3295. result_ctx->more_info_dialog = NULL;
  3296. strbuf *moreinfo = strbuf_new();
  3297. for (SeatDialogTextItem *item = text->items,
  3298. *end = item + text->nitems; item < end; item++) {
  3299. switch (item->type) {
  3300. case SDT_MORE_INFO_KEY:
  3301. put_fmt(moreinfo, "%s", item->text);
  3302. break;
  3303. case SDT_MORE_INFO_VALUE_SHORT:
  3304. put_fmt(moreinfo, ": %s\n", item->text);
  3305. break;
  3306. case SDT_MORE_INFO_VALUE_BLOB:
  3307. /* We have to manually wrap the public key, or else the GtkLabel
  3308. * will resize itself to accommodate the longest word, which will
  3309. * lead to a hilariously wide message box. */
  3310. put_byte(moreinfo, ':');
  3311. for (const char *p = item->text, *q = p + strlen(p); p < q ;) {
  3312. size_t linelen = q-p;
  3313. if (linelen > 72)
  3314. linelen = 72;
  3315. put_byte(moreinfo, '\n');
  3316. put_data(moreinfo, p, linelen);
  3317. p += linelen;
  3318. }
  3319. put_byte(moreinfo, '\n');
  3320. break;
  3321. default:
  3322. break;
  3323. }
  3324. }
  3325. result_ctx->more_info = strbuf_to_str(moreinfo);
  3326. g_signal_connect(G_OBJECT(more_info_button), "clicked",
  3327. G_CALLBACK(more_info_button_clicked), result_ctx);
  3328. register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox);
  3329. strbuf_free(dlg_text);
  3330. return SPR_INCOMPLETE; /* dialog still in progress */
  3331. }
  3332. struct simple_prompt_result_spr_ctx {
  3333. void (*callback)(void *callback_ctx, SeatPromptResult spr);
  3334. void *callback_ctx;
  3335. Seat *seat;
  3336. enum DialogSlot dialog_slot;
  3337. };
  3338. static void simple_prompt_result_spr_callback(void *vctx, int result)
  3339. {
  3340. struct simple_prompt_result_spr_ctx *ctx =
  3341. (struct simple_prompt_result_spr_ctx *)vctx;
  3342. unregister_dialog(ctx->seat, ctx->dialog_slot);
  3343. if (result == 0)
  3344. ctx->callback(ctx->callback_ctx, SPR_USER_ABORT);
  3345. else if (result > 0)
  3346. ctx->callback(ctx->callback_ctx, SPR_OK);
  3347. /* if <0, we're cleaning up for some other reason */
  3348. /*
  3349. * Clean up this context structure, whether or not a result was
  3350. * ever actually delivered from the dialog box.
  3351. */
  3352. sfree(ctx);
  3353. }
  3354. /*
  3355. * Ask whether the selected algorithm is acceptable (since it was
  3356. * below the configured 'warn' threshold).
  3357. */
  3358. SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
  3359. Seat *seat, SeatDialogText *text,
  3360. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3361. {
  3362. struct simple_prompt_result_spr_ctx *result_ctx;
  3363. GtkWidget *mainwin, *msgbox;
  3364. int width = string_width("Reasonably long line of text "
  3365. "as a width template");
  3366. strbuf *dlg_text = strbuf_new();
  3367. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3368. result_ctx = snew(struct simple_prompt_result_spr_ctx);
  3369. result_ctx->callback = callback;
  3370. result_ctx->callback_ctx = ctx;
  3371. result_ctx->seat = seat;
  3372. result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
  3373. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3374. msgbox = create_message_box(
  3375. mainwin, dlg_title, dlg_text->s, width, false,
  3376. &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
  3377. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3378. strbuf_free(dlg_text);
  3379. return SPR_INCOMPLETE;
  3380. }
  3381. SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
  3382. Seat *seat, SeatDialogText *text,
  3383. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  3384. {
  3385. struct simple_prompt_result_spr_ctx *result_ctx;
  3386. GtkWidget *mainwin, *msgbox;
  3387. int width = string_width("is ecdsa-nistp521, which is below the configured"
  3388. " warning threshold.");
  3389. strbuf *dlg_text = strbuf_new();
  3390. const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
  3391. result_ctx = snew(struct simple_prompt_result_spr_ctx);
  3392. result_ctx->callback = callback;
  3393. result_ctx->callback_ctx = ctx;
  3394. result_ctx->seat = seat;
  3395. result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
  3396. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3397. msgbox = create_message_box(
  3398. mainwin, dlg_title, dlg_text->s, width, false,
  3399. &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
  3400. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3401. strbuf_free(dlg_text);
  3402. return SPR_INCOMPLETE;
  3403. }
  3404. void old_keyfile_warning(void)
  3405. {
  3406. /*
  3407. * This should never happen on Unix. We hope.
  3408. */
  3409. }
  3410. void nonfatal_message_box(void *window, const char *msg)
  3411. {
  3412. char *title = dupcat(appname, " Error");
  3413. create_message_box(
  3414. window, title, msg,
  3415. string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
  3416. false, &buttons_ok, trivial_post_dialog_fn, NULL);
  3417. sfree(title);
  3418. }
  3419. void nonfatal(const char *p, ...)
  3420. {
  3421. va_list ap;
  3422. char *msg;
  3423. va_start(ap, p);
  3424. msg = dupvprintf(p, ap);
  3425. va_end(ap);
  3426. nonfatal_message_box(NULL, msg);
  3427. sfree(msg);
  3428. }
  3429. static GtkWidget *aboutbox = NULL;
  3430. static void about_window_destroyed(GtkWidget *widget, gpointer data)
  3431. {
  3432. aboutbox = NULL;
  3433. }
  3434. static void about_close_clicked(GtkButton *button, gpointer data)
  3435. {
  3436. gtk_widget_destroy(aboutbox);
  3437. aboutbox = NULL;
  3438. }
  3439. static void about_key_press(GtkWidget *widget, GdkEventKey *event,
  3440. gpointer data)
  3441. {
  3442. if (event->keyval == GDK_KEY_Escape && aboutbox) {
  3443. gtk_widget_destroy(aboutbox);
  3444. aboutbox = NULL;
  3445. }
  3446. }
  3447. static void licence_clicked(GtkButton *button, gpointer data)
  3448. {
  3449. char *title;
  3450. title = dupcat(appname, " Licence");
  3451. assert(aboutbox != NULL);
  3452. create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"),
  3453. string_width("LONGISH LINE OF TEXT SO THE LICENCE"
  3454. " BOX ISN'T EXCESSIVELY TALL AND THIN"),
  3455. true, &buttons_ok, trivial_post_dialog_fn, NULL);
  3456. sfree(title);
  3457. }
  3458. void about_box(void *window)
  3459. {
  3460. GtkWidget *w;
  3461. GtkBox *action_area;
  3462. char *title;
  3463. if (aboutbox) {
  3464. gtk_widget_grab_focus(aboutbox);
  3465. return;
  3466. }
  3467. aboutbox = our_dialog_new();
  3468. gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
  3469. title = dupcat("About ", appname);
  3470. gtk_window_set_title(GTK_WINDOW(aboutbox), title);
  3471. sfree(title);
  3472. g_signal_connect(G_OBJECT(aboutbox), "destroy",
  3473. G_CALLBACK(about_window_destroyed), NULL);
  3474. w = gtk_button_new_with_label("Close");
  3475. gtk_widget_set_can_default(w, true);
  3476. gtk_window_set_default(GTK_WINDOW(aboutbox), w);
  3477. action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
  3478. gtk_box_pack_end(action_area, w, false, false, 0);
  3479. g_signal_connect(G_OBJECT(w), "clicked",
  3480. G_CALLBACK(about_close_clicked), NULL);
  3481. gtk_widget_show(w);
  3482. w = gtk_button_new_with_label("View Licence");
  3483. gtk_widget_set_can_default(w, true);
  3484. gtk_box_pack_end(action_area, w, false, false, 0);
  3485. g_signal_connect(G_OBJECT(w), "clicked",
  3486. G_CALLBACK(licence_clicked), NULL);
  3487. gtk_widget_show(w);
  3488. {
  3489. char *buildinfo_text = buildinfo("\n");
  3490. char *label_text = dupprintf(
  3491. "%s\n\n%s\n\n%s\n\n%s",
  3492. appname, ver, buildinfo_text,
  3493. "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved");
  3494. w = gtk_label_new(label_text);
  3495. gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
  3496. #if GTK_CHECK_VERSION(2,0,0)
  3497. gtk_label_set_selectable(GTK_LABEL(w), true);
  3498. #endif
  3499. sfree(label_text);
  3500. }
  3501. our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0);
  3502. #if GTK_CHECK_VERSION(2,0,0)
  3503. /*
  3504. * Same precautions against initial select-all as in
  3505. * create_message_box().
  3506. */
  3507. gtk_widget_grab_focus(w);
  3508. gtk_label_select_region(GTK_LABEL(w), 0, 0);
  3509. #endif
  3510. gtk_widget_show(w);
  3511. g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
  3512. G_CALLBACK(about_key_press), NULL);
  3513. set_transient_window_pos(GTK_WIDGET(window), aboutbox);
  3514. if (window)
  3515. gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
  3516. GTK_WINDOW(window));
  3517. gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
  3518. gtk_widget_show(aboutbox);
  3519. gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
  3520. }
  3521. #define LOGEVENT_INITIAL_MAX 128
  3522. #define LOGEVENT_CIRCULAR_MAX 128
  3523. struct eventlog_stuff {
  3524. GtkWidget *parentwin, *window;
  3525. struct controlbox *eventbox;
  3526. struct Shortcuts scs;
  3527. struct dlgparam dp;
  3528. dlgcontrol *listctrl;
  3529. char **events_initial;
  3530. char **events_circular;
  3531. int ninitial, ncircular, circular_first;
  3532. strbuf *seldata;
  3533. int sellen;
  3534. bool ignore_selchange;
  3535. };
  3536. static void eventlog_destroy(GtkWidget *widget, gpointer data)
  3537. {
  3538. eventlog_stuff *es = (eventlog_stuff *)data;
  3539. es->window = NULL;
  3540. dlg_cleanup(&es->dp);
  3541. ctrl_free_box(es->eventbox);
  3542. }
  3543. static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
  3544. void *data, int event)
  3545. {
  3546. if (event == EVENT_ACTION)
  3547. dlg_end(dp, 0);
  3548. }
  3549. static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp,
  3550. void *data, int event)
  3551. {
  3552. eventlog_stuff *es = (eventlog_stuff *)data;
  3553. if (event == EVENT_REFRESH) {
  3554. int i;
  3555. dlg_update_start(ctrl, dp);
  3556. dlg_listbox_clear(ctrl, dp);
  3557. for (i = 0; i < es->ninitial; i++) {
  3558. dlg_listbox_add(ctrl, dp, es->events_initial[i]);
  3559. }
  3560. for (i = 0; i < es->ncircular; i++) {
  3561. dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
  3562. }
  3563. dlg_update_done(ctrl, dp);
  3564. } else if (event == EVENT_SELCHANGE) {
  3565. int i;
  3566. /*
  3567. * If this SELCHANGE event is happening as a result of
  3568. * deliberate deselection because someone else has grabbed
  3569. * the selection, the last thing we want to do is pre-empt
  3570. * them.
  3571. */
  3572. if (es->ignore_selchange)
  3573. return;
  3574. /*
  3575. * Construct the data to use as the selection.
  3576. */
  3577. strbuf_clear(es->seldata);
  3578. for (i = 0; i < es->ninitial; i++) {
  3579. if (dlg_listbox_issel(ctrl, dp, i))
  3580. put_fmt(es->seldata, "%s\n", es->events_initial[i]);
  3581. }
  3582. for (i = 0; i < es->ncircular; i++) {
  3583. if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) {
  3584. int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX;
  3585. put_fmt(es->seldata, "%s\n", es->events_circular[j]);
  3586. }
  3587. }
  3588. if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
  3589. GDK_CURRENT_TIME)) {
  3590. gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
  3591. GDK_SELECTION_TYPE_STRING, 1);
  3592. gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
  3593. compound_text_atom, 1);
  3594. }
  3595. }
  3596. }
  3597. void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
  3598. guint info, guint time_stamp, gpointer data)
  3599. {
  3600. eventlog_stuff *es = (eventlog_stuff *)data;
  3601. gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
  3602. es->seldata->u, es->seldata->len);
  3603. }
  3604. gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
  3605. gpointer data)
  3606. {
  3607. eventlog_stuff *es = (eventlog_stuff *)data;
  3608. struct uctrl *uc;
  3609. /*
  3610. * Deselect everything in the list box.
  3611. */
  3612. uc = dlg_find_byctrl(&es->dp, es->listctrl);
  3613. es->ignore_selchange = true;
  3614. #if !GTK_CHECK_VERSION(2,0,0)
  3615. assert(uc->list);
  3616. gtk_list_unselect_all(GTK_LIST(uc->list));
  3617. #else
  3618. assert(uc->treeview);
  3619. gtk_tree_selection_unselect_all(
  3620. gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
  3621. #endif
  3622. es->ignore_selchange = false;
  3623. return true;
  3624. }
  3625. void showeventlog(eventlog_stuff *es, void *parentwin)
  3626. {
  3627. GtkWidget *window, *w0, *w1;
  3628. GtkWidget *parent = GTK_WIDGET(parentwin);
  3629. struct controlset *s0, *s1;
  3630. dlgcontrol *c;
  3631. int index;
  3632. char *title;
  3633. if (es->window) {
  3634. gtk_widget_grab_focus(es->window);
  3635. return;
  3636. }
  3637. dlg_init(&es->dp);
  3638. for (index = 0; index < lenof(es->scs.sc); index++) {
  3639. es->scs.sc[index].action = SHORTCUT_EMPTY;
  3640. }
  3641. es->eventbox = ctrl_new_box();
  3642. s0 = ctrl_getset(es->eventbox, "", "", "");
  3643. ctrl_columns(s0, 3, 33, 34, 33);
  3644. c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
  3645. eventlog_ok_handler, P(NULL));
  3646. c->column = 1;
  3647. c->button.isdefault = true;
  3648. s1 = ctrl_getset(es->eventbox, "x", "", "");
  3649. es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
  3650. eventlog_list_handler, P(es));
  3651. c->listbox.height = 10;
  3652. c->listbox.multisel = 2;
  3653. c->listbox.ncols = 3;
  3654. c->listbox.percentages = snewn(3, int);
  3655. c->listbox.percentages[0] = 25;
  3656. c->listbox.percentages[1] = 10;
  3657. c->listbox.percentages[2] = 65;
  3658. es->window = window = our_dialog_new();
  3659. title = dupcat(appname, " Event Log");
  3660. gtk_window_set_title(GTK_WINDOW(window), title);
  3661. sfree(title);
  3662. w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window));
  3663. our_dialog_set_action_area(GTK_WINDOW(window), w0);
  3664. gtk_widget_show(w0);
  3665. w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window));
  3666. gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
  3667. gtk_widget_set_size_request(
  3668. w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
  3669. "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
  3670. -1);
  3671. our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
  3672. {
  3673. struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl);
  3674. columns_vexpand(COLUMNS(w1), uc->toplevel);
  3675. }
  3676. gtk_widget_show(w1);
  3677. es->dp.data = es;
  3678. es->dp.shortcuts = &es->scs;
  3679. es->dp.lastfocus = NULL;
  3680. es->dp.retval = 0;
  3681. es->dp.window = window;
  3682. dlg_refresh(NULL, &es->dp);
  3683. if (parent) {
  3684. set_transient_window_pos(parent, window);
  3685. gtk_window_set_transient_for(GTK_WINDOW(window),
  3686. GTK_WINDOW(parent));
  3687. } else
  3688. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  3689. gtk_widget_show(window);
  3690. g_signal_connect(G_OBJECT(window), "destroy",
  3691. G_CALLBACK(eventlog_destroy), es);
  3692. g_signal_connect(G_OBJECT(window), "key_press_event",
  3693. G_CALLBACK(win_key_press), &es->dp);
  3694. g_signal_connect(G_OBJECT(window), "selection_get",
  3695. G_CALLBACK(eventlog_selection_get), es);
  3696. g_signal_connect(G_OBJECT(window), "selection_clear_event",
  3697. G_CALLBACK(eventlog_selection_clear), es);
  3698. }
  3699. eventlog_stuff *eventlogstuff_new(void)
  3700. {
  3701. eventlog_stuff *es = snew(eventlog_stuff);
  3702. memset(es, 0, sizeof(*es));
  3703. es->seldata = strbuf_new();
  3704. return es;
  3705. }
  3706. void eventlogstuff_free(eventlog_stuff *es)
  3707. {
  3708. int i;
  3709. if (es->events_initial) {
  3710. for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
  3711. sfree(es->events_initial[i]);
  3712. sfree(es->events_initial);
  3713. }
  3714. if (es->events_circular) {
  3715. for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
  3716. sfree(es->events_circular[i]);
  3717. sfree(es->events_circular);
  3718. }
  3719. strbuf_free(es->seldata);
  3720. sfree(es);
  3721. }
  3722. void logevent_dlg(eventlog_stuff *es, const char *string)
  3723. {
  3724. char timebuf[40];
  3725. struct tm tm;
  3726. char **location;
  3727. size_t i;
  3728. if (es->ninitial == 0) {
  3729. es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *);
  3730. for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
  3731. es->events_initial[i] = NULL;
  3732. es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *);
  3733. for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
  3734. es->events_circular[i] = NULL;
  3735. }
  3736. if (es->ninitial < LOGEVENT_INITIAL_MAX)
  3737. location = &es->events_initial[es->ninitial];
  3738. else
  3739. location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX];
  3740. tm=ltime();
  3741. strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
  3742. sfree(*location);
  3743. *location = dupcat(timebuf, string);
  3744. if (es->window) {
  3745. dlg_listbox_add(es->listctrl, &es->dp, *location);
  3746. }
  3747. if (es->ninitial < LOGEVENT_INITIAL_MAX) {
  3748. es->ninitial++;
  3749. } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) {
  3750. es->ncircular++;
  3751. } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) {
  3752. es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
  3753. sfree(es->events_circular[es->circular_first]);
  3754. es->events_circular[es->circular_first] = dupstr("..");
  3755. }
  3756. }
  3757. struct simple_prompt_result_int_ctx {
  3758. void (*callback)(void *callback_ctx, int result);
  3759. void *callback_ctx;
  3760. Seat *seat;
  3761. enum DialogSlot dialog_slot;
  3762. };
  3763. static void simple_prompt_result_int_callback(void *vctx, int result)
  3764. {
  3765. struct simple_prompt_result_int_ctx *ctx =
  3766. (struct simple_prompt_result_int_ctx *)vctx;
  3767. unregister_dialog(ctx->seat, ctx->dialog_slot);
  3768. if (result >= 0)
  3769. ctx->callback(ctx->callback_ctx, result);
  3770. /*
  3771. * Clean up this context structure, whether or not a result was
  3772. * ever actually delivered from the dialog box.
  3773. */
  3774. sfree(ctx);
  3775. }
  3776. int gtkdlg_askappend(Seat *seat, Filename *filename,
  3777. void (*callback)(void *ctx, int result), void *ctx)
  3778. {
  3779. static const char msgtemplate[] =
  3780. "The session log file \"%.*s\" already exists. "
  3781. "You can overwrite it with a new session log, "
  3782. "append your session log to the end of it, "
  3783. "or disable session logging for this session.";
  3784. static const struct message_box_button button_array_append[] = {
  3785. {"Overwrite", 'o', 1, 2},
  3786. {"Append", 'a', 0, 1},
  3787. {"Disable", 'd', -1, 0},
  3788. };
  3789. static const struct message_box_buttons buttons_append = {
  3790. button_array_append, lenof(button_array_append),
  3791. };
  3792. char *message;
  3793. char *mbtitle;
  3794. struct simple_prompt_result_int_ctx *result_ctx;
  3795. GtkWidget *mainwin, *msgbox;
  3796. message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
  3797. mbtitle = dupprintf("%s Log to File", appname);
  3798. result_ctx = snew(struct simple_prompt_result_int_ctx);
  3799. result_ctx->callback = callback;
  3800. result_ctx->callback_ctx = ctx;
  3801. result_ctx->seat = seat;
  3802. result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT;
  3803. mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
  3804. msgbox = create_message_box(
  3805. mainwin, mbtitle, message,
  3806. string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"),
  3807. false, &buttons_append, simple_prompt_result_int_callback, result_ctx);
  3808. register_dialog(seat, result_ctx->dialog_slot, msgbox);
  3809. sfree(message);
  3810. sfree(mbtitle);
  3811. return -1; /* dialog still in progress */
  3812. }
  3813. struct ca_config_box {
  3814. GtkWidget *window;
  3815. struct controlbox *cb;
  3816. struct Shortcuts scs;
  3817. bool quit_main;
  3818. dlgparam dp;
  3819. };
  3820. static struct ca_config_box *cacfg; /* one of these, cross-instance */
  3821. static void cacfg_destroy(GtkWidget *widget, gpointer data)
  3822. {
  3823. cacfg->window = NULL;
  3824. dlg_cleanup(&cacfg->dp);
  3825. ctrl_free_box(cacfg->cb);
  3826. cacfg->cb = NULL;
  3827. if (cacfg->quit_main)
  3828. gtk_main_quit();
  3829. }
  3830. static void make_ca_config_box(GtkWidget *spawning_window)
  3831. {
  3832. if (!cacfg) {
  3833. cacfg = snew(struct ca_config_box);
  3834. memset(cacfg, 0, sizeof(*cacfg));
  3835. }
  3836. if (cacfg->window) {
  3837. /* This dialog box is already displayed; re-focus it */
  3838. gtk_widget_grab_focus(cacfg->window);
  3839. return;
  3840. }
  3841. dlg_init(&cacfg->dp);
  3842. for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) {
  3843. cacfg->scs.sc[i].action = SHORTCUT_EMPTY;
  3844. }
  3845. cacfg->cb = ctrl_new_box();
  3846. setup_ca_config_box(cacfg->cb);
  3847. cacfg->window = our_dialog_new();
  3848. gtk_window_set_title(GTK_WINDOW(cacfg->window),
  3849. "PuTTY trusted host certification authorities");
  3850. gtk_widget_set_size_request(
  3851. cacfg->window, string_width(
  3852. "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5"
  3853. "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1);
  3854. /* Set up everything else */
  3855. for (int i = 0; i < cacfg->cb->nctrlsets; i++) {
  3856. struct controlset *s = cacfg->cb->ctrlsets[i];
  3857. GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s,
  3858. GTK_WINDOW(cacfg->window));
  3859. gtk_container_set_border_width(GTK_CONTAINER(w), 10);
  3860. gtk_widget_show(w);
  3861. if (!*s->pathname) {
  3862. our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w);
  3863. } else {
  3864. our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w,
  3865. true, true, 0);
  3866. }
  3867. }
  3868. cacfg->dp.data = cacfg;
  3869. cacfg->dp.shortcuts = &cacfg->scs;
  3870. cacfg->dp.lastfocus = NULL;
  3871. cacfg->dp.retval = 0;
  3872. cacfg->dp.window = cacfg->window;
  3873. dlg_refresh(NULL, &cacfg->dp);
  3874. if (spawning_window) {
  3875. set_transient_window_pos(spawning_window, cacfg->window);
  3876. } else {
  3877. gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER);
  3878. }
  3879. gtk_widget_show(cacfg->window);
  3880. g_signal_connect(G_OBJECT(cacfg->window), "destroy",
  3881. G_CALLBACK(cacfg_destroy), NULL);
  3882. g_signal_connect(G_OBJECT(cacfg->window), "key_press_event",
  3883. G_CALLBACK(win_key_press), &cacfg->dp);
  3884. }
  3885. void show_ca_config_box(dlgparam *dp)
  3886. {
  3887. make_ca_config_box(dp ? dp->window : NULL);
  3888. }
  3889. void show_ca_config_box_synchronously(void)
  3890. {
  3891. make_ca_config_box(NULL);
  3892. cacfg->quit_main = true;
  3893. gtk_main();
  3894. }