dialog.c 147 KB

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