gtkdlg.c 124 KB

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