Helios-Core.st 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. Smalltalk current createPackage: 'Helios-Core'!
  2. Object subclass: #HLModel
  3. instanceVariableNames: 'announcer environment'
  4. package: 'Helios-Core'!
  5. !HLModel methodsFor: 'accessing'!
  6. announcer
  7. ^ announcer ifNil: [ announcer := Announcer new ]
  8. !
  9. environment
  10. ^ environment ifNil: [ self manager environment ]
  11. !
  12. environment: anEnvironment
  13. environment := anEnvironment
  14. !
  15. manager
  16. ^ HLManager current
  17. !
  18. systemAnnouncer
  19. ^ self environment systemAnnouncer
  20. ! !
  21. !HLModel methodsFor: 'error handling'!
  22. withChangesDo: aBlock
  23. [
  24. self announcer announce: (HLAboutToChange new
  25. actionBlock: aBlock).
  26. aBlock value.
  27. ]
  28. on: HLChangeForbidden
  29. do: [ :ex | ]
  30. ! !
  31. Widget subclass: #HLTab
  32. instanceVariableNames: 'widget label root'
  33. package: 'Helios-Core'!
  34. !HLTab methodsFor: 'accessing'!
  35. activate
  36. self manager activate: self
  37. !
  38. add
  39. self manager addTab: self
  40. !
  41. displayLabel
  42. ^ self label size > 20
  43. ifTrue: [ (self label first: 20), '...' ]
  44. ifFalse: [ self label ]
  45. !
  46. focus
  47. self widget canHaveFocus ifTrue: [
  48. self widget focus ]
  49. !
  50. label
  51. ^ label ifNil: [ '' ]
  52. !
  53. label: aString
  54. label := aString
  55. !
  56. manager
  57. ^ HLManager current
  58. !
  59. widget
  60. ^ widget
  61. !
  62. widget: aWidget
  63. widget := aWidget
  64. ! !
  65. !HLTab methodsFor: 'actions'!
  66. hide
  67. root ifNotNil: [ root asJQuery css: 'visibility' put: 'hidden' ]
  68. !
  69. registerBindings
  70. self widget registerBindings
  71. !
  72. remove
  73. root ifNotNil: [ root asJQuery remove ]
  74. !
  75. show
  76. root
  77. ifNil: [ self appendToJQuery: 'body' asJQuery ]
  78. ifNotNil: [ root asJQuery css: 'visibility' put: 'visible' ]
  79. ! !
  80. !HLTab methodsFor: 'rendering'!
  81. renderOn: html
  82. root := html div
  83. class: 'tab';
  84. yourself.
  85. self renderTab
  86. !
  87. renderTab
  88. root contents: [ :html |
  89. html div
  90. class: 'amber_box';
  91. with: [ self widget renderOn: html ] ]
  92. ! !
  93. !HLTab methodsFor: 'testing'!
  94. isActive
  95. ^ self manager activeTab = self
  96. ! !
  97. !HLTab class methodsFor: 'instance creation'!
  98. on: aWidget labelled: aString
  99. ^ self new
  100. widget: aWidget;
  101. label: aString;
  102. yourself
  103. ! !
  104. Widget subclass: #HLWidget
  105. instanceVariableNames: 'wrapper'
  106. package: 'Helios-Core'!
  107. !HLWidget methodsFor: 'accessing'!
  108. manager
  109. ^ HLManager current
  110. !
  111. wrapper
  112. ^ wrapper
  113. ! !
  114. !HLWidget methodsFor: 'actions'!
  115. alert: aString
  116. window alert: aString
  117. !
  118. confirm: aString ifTrue: aBlock
  119. self manager confirm: aString ifTrue: aBlock
  120. !
  121. execute: aCommand
  122. HLManager current keyBinder
  123. activate;
  124. applyBinding: aCommand asBinding
  125. ! !
  126. !HLWidget methodsFor: 'keybindings'!
  127. registerBindings
  128. self registerBindingsOn: self manager keyBinder bindings
  129. !
  130. registerBindingsOn: aBindingGroup
  131. ! !
  132. !HLWidget methodsFor: 'rendering'!
  133. renderContentOn: html
  134. !
  135. renderOn: html
  136. wrapper := html div.
  137. [ :renderer | self renderContentOn: renderer ] appendToJQuery: wrapper asJQuery
  138. ! !
  139. !HLWidget methodsFor: 'testing'!
  140. canHaveFocus
  141. ^ false
  142. ! !
  143. !HLWidget methodsFor: 'updating'!
  144. refresh
  145. self wrapper ifNil: [ ^ self ].
  146. self wrapper asJQuery empty.
  147. [ :html | self renderContentOn: html ] appendToJQuery: self wrapper asJQuery
  148. ! !
  149. !HLWidget class methodsFor: 'accessing'!
  150. openAsTab
  151. self canBeOpenAsTab ifFalse: [ ^ self ].
  152. HLManager current addTab: (HLTab on: self new labelled: self tabLabel)
  153. !
  154. tabLabel
  155. ^ 'Tab'
  156. !
  157. tabPriority
  158. ^ 500
  159. ! !
  160. !HLWidget class methodsFor: 'testing'!
  161. canBeOpenAsTab
  162. ^ false
  163. ! !
  164. HLWidget subclass: #HLConfirmation
  165. instanceVariableNames: 'confirmationString actionBlock cancelBlock'
  166. package: 'Helios-Core'!
  167. !HLConfirmation methodsFor: 'accessing'!
  168. actionBlock
  169. ^ actionBlock ifNil: [ [] ]
  170. !
  171. actionBlock: aBlock
  172. actionBlock := aBlock
  173. !
  174. cancelBlock
  175. ^ cancelBlock ifNil: [ [] ]
  176. !
  177. cancelBlock: aBlock
  178. cancelBlock := aBlock
  179. !
  180. confirmationString
  181. ^ confirmationString ifNil: [ 'Confirm' ]
  182. !
  183. confirmationString: aString
  184. confirmationString := aString
  185. ! !
  186. !HLConfirmation methodsFor: 'actions'!
  187. cancel
  188. self cancelBlock value.
  189. self remove
  190. !
  191. confirm
  192. self actionBlock value.
  193. self remove
  194. !
  195. remove
  196. (window jQuery: '.confirmation') removeClass: 'active'.
  197. [
  198. (window jQuery: '#overlay') remove.
  199. (window jQuery: '.confirmation') remove
  200. ] valueWithTimeout: 300
  201. ! !
  202. !HLConfirmation methodsFor: 'rendering'!
  203. renderContentOn: html
  204. | confirmButton |
  205. html div id: 'overlay'.
  206. html div
  207. class: 'confirmation';
  208. with: [
  209. html span with: self confirmationString.
  210. html div
  211. class: 'buttons';
  212. with: [
  213. html button
  214. class: 'button';
  215. with: 'Cancel';
  216. onClick: [ self cancel ].
  217. confirmButton := html button
  218. class: 'button default';
  219. with: 'Confirm';
  220. onClick: [ self confirm ] ] ].
  221. confirmButton asJQuery focus.
  222. (window jQuery: '.confirmation') addClass: 'active'.
  223. self setupKeyBindings
  224. !
  225. setupKeyBindings
  226. (window jQuery: '.confirmation') keyup: [ :e |
  227. e keyCode = 27 ifTrue: [ self cancel ] ]
  228. ! !
  229. HLWidget subclass: #HLDebugger
  230. instanceVariableNames: ''
  231. package: 'Helios-Core'!
  232. HLWidget subclass: #HLFocusableWidget
  233. instanceVariableNames: ''
  234. package: 'Helios-Core'!
  235. !HLFocusableWidget methodsFor: 'accessing'!
  236. focusClass
  237. ^ 'focused'
  238. ! !
  239. !HLFocusableWidget methodsFor: 'events'!
  240. blur
  241. self wrapper asJQuery blur
  242. !
  243. focus
  244. self wrapper asJQuery focus
  245. !
  246. hasFocus
  247. ^ self wrapper notNil and: [ self wrapper asJQuery is: ':focus' ]
  248. ! !
  249. !HLFocusableWidget methodsFor: 'rendering'!
  250. renderContentOn: html
  251. !
  252. renderOn: html
  253. self registerBindings.
  254. wrapper := html div
  255. class: 'hl_widget';
  256. yourself.
  257. wrapper with: [ self renderContentOn: html ].
  258. wrapper
  259. at: 'tabindex' put: '0';
  260. onBlur: [ self wrapper asJQuery removeClass: self focusClass ];
  261. onFocus: [ self wrapper asJQuery addClass: self focusClass ]
  262. ! !
  263. !HLFocusableWidget methodsFor: 'testing'!
  264. canHaveFocus
  265. ^ true
  266. ! !
  267. HLFocusableWidget subclass: #HLListWidget
  268. instanceVariableNames: 'items selectedItem mapping'
  269. package: 'Helios-Core'!
  270. !HLListWidget methodsFor: 'accessing'!
  271. cssClassForItem: anObject
  272. ^ self selectedItem = anObject
  273. ifTrue: [ 'active' ]
  274. ifFalse: [ 'inactive' ]
  275. !
  276. iconForItem: anObject
  277. ^ ''
  278. !
  279. items
  280. ^ items ifNil: [ items := self defaultItems ]
  281. !
  282. items: aCollection
  283. items := aCollection
  284. !
  285. positionOf: aListItem
  286. <
  287. return aListItem.parent().children().get().indexOf(aListItem.get(0)) + 1
  288. >
  289. !
  290. selectedItem
  291. ^ selectedItem
  292. !
  293. selectedItem: anObject
  294. selectedItem := anObject
  295. ! !
  296. !HLListWidget methodsFor: 'actions'!
  297. activateFirstListItem
  298. self activateListItem: (window jQuery: ((wrapper asJQuery find: 'li.inactive') get: 0))
  299. !
  300. activateItem: anObject
  301. self activateListItem: (mapping
  302. at: anObject
  303. ifAbsent: [ ^ self ]) asJQuery
  304. !
  305. activateListItem: aListItem
  306. | item |
  307. (aListItem get: 0) ifNil: [ ^self ].
  308. aListItem parent children removeClass: 'active'.
  309. aListItem addClass: 'active'.
  310. self ensureVisible: aListItem.
  311. "Activate the corresponding item"
  312. item := (self items at: (aListItem attr: 'list-data') asNumber).
  313. self selectedItem == item ifFalse: [
  314. self selectItem: item ]
  315. !
  316. activateNextListItem
  317. self activateListItem: (window jQuery: '.focused .nav-pills .active') next.
  318. "select the first item if none is selected"
  319. (window jQuery: '.focused .nav-pills .active') get ifEmpty: [
  320. self activateFirstListItem ]
  321. !
  322. activatePreviousListItem
  323. self activateListItem: (window jQuery: '.focused .nav-pills .active') prev
  324. !
  325. ensureVisible: aListItem
  326. "Move the scrollbar to show the active element"
  327. | perent position |
  328. position := self positionOf: aListItem.
  329. parent := aListItem parent.
  330. aListItem position top < 0 ifTrue: [
  331. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].
  332. aListItem position top + aListItem height > parent height ifTrue: [
  333. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ]
  334. !
  335. focus
  336. super focus.
  337. self items isEmpty ifFalse: [
  338. self selectedItem ifNil: [ self activateFirstListItem ] ]
  339. !
  340. refresh
  341. super refresh.
  342. self ensureVisible: (mapping
  343. at: self selectedItem
  344. ifAbsent: [ ^ self ]) asJQuery
  345. !
  346. selectItem: anObject
  347. self selectedItem: anObject
  348. ! !
  349. !HLListWidget methodsFor: 'defaults'!
  350. defaultItems
  351. ^ #()
  352. ! !
  353. !HLListWidget methodsFor: 'events'!
  354. setupKeyBindings
  355. "TODO: refactor this!!"
  356. | active interval delay repeatInterval |
  357. active := false.
  358. repeatInterval := 70.
  359. self wrapper asJQuery unbind: 'keydown'.
  360. self wrapper asJQuery keydown: [ :e |
  361. (e which = 38 and: [ active = false ]) ifTrue: [
  362. active := true.
  363. self activatePreviousListItem.
  364. delay := [
  365. interval := [
  366. (self wrapper asJQuery hasClass: self focusClass)
  367. ifTrue: [
  368. self activatePreviousListItem ]
  369. ifFalse: [
  370. active := false.
  371. interval ifNotNil: [ interval clearInterval ].
  372. delay ifNotNil: [ delay clearTimeout] ] ]
  373. valueWithInterval: repeatInterval ]
  374. valueWithTimeout: 300 ].
  375. (e which = 40 and: [ active = false ]) ifTrue: [
  376. active := true.
  377. self activateNextListItem.
  378. delay := [
  379. interval := [
  380. (self wrapper asJQuery hasClass: self focusClass)
  381. ifTrue: [
  382. self activateNextListItem ]
  383. ifFalse: [
  384. active := false.
  385. interval ifNotNil: [ interval clearInterval ].
  386. delay ifNotNil: [ delay clearTimeout] ] ]
  387. valueWithInterval: repeatInterval ]
  388. valueWithTimeout: 300 ] ].
  389. self wrapper asJQuery keyup: [ :e |
  390. active ifTrue: [
  391. active := false.
  392. interval ifNotNil: [ interval clearInterval ].
  393. delay ifNotNil: [ delay clearTimeout] ] ]
  394. ! !
  395. !HLListWidget methodsFor: 'initialization'!
  396. initialize
  397. super initialize.
  398. mapping := Dictionary new.
  399. ! !
  400. !HLListWidget methodsFor: 'private'!
  401. registerMappingFrom: anObject to: aTag
  402. mapping at: anObject put: aTag
  403. ! !
  404. !HLListWidget methodsFor: 'rendering'!
  405. renderButtonsOn: html
  406. !
  407. renderContentOn: html
  408. html ul
  409. class: 'nav nav-pills nav-stacked';
  410. with: [ self renderListOn: html ].
  411. html div class: 'pane_actions form-actions'; with: [
  412. self renderButtonsOn: html ].
  413. self setupKeyBindings
  414. !
  415. renderItem: anObject on: html
  416. | li |
  417. li := html li.
  418. self registerMappingFrom: anObject to: li.
  419. li
  420. class: (self cssClassForItem: anObject);
  421. at: 'list-data' put: (self items indexOf: anObject) asString;
  422. with: [
  423. html a
  424. with: [
  425. (html tag: 'i') class: (self iconForItem: anObject).
  426. self renderItemLabel: anObject on: html ];
  427. onClick: [
  428. self activateListItem: li asJQuery ] ]
  429. !
  430. renderItemLabel: anObject on: html
  431. html with: anObject asString
  432. !
  433. renderListOn: html
  434. mapping := Dictionary new.
  435. self items do: [ :each |
  436. self renderItem: each on: html ]
  437. ! !
  438. HLListWidget subclass: #HLNavigationListWidget
  439. instanceVariableNames: 'previous next'
  440. package: 'Helios-Core'!
  441. !HLNavigationListWidget methodsFor: 'accessing'!
  442. next
  443. ^ next
  444. !
  445. next: aWidget
  446. next := aWidget.
  447. aWidget previous = self ifFalse: [ aWidget previous: self ]
  448. !
  449. previous
  450. ^ previous
  451. !
  452. previous: aWidget
  453. previous := aWidget.
  454. aWidget next = self ifFalse: [ aWidget next: self ]
  455. ! !
  456. !HLNavigationListWidget methodsFor: 'actions'!
  457. nextFocus
  458. self next ifNotNil: [ self next focus ]
  459. !
  460. previousFocus
  461. self previous ifNotNil: [ self previous focus ]
  462. ! !
  463. !HLNavigationListWidget methodsFor: 'events'!
  464. setupKeyBindings
  465. super setupKeyBindings.
  466. self wrapper asJQuery keydown: [ :e |
  467. e which = 39 ifTrue: [
  468. self nextFocus ].
  469. e which = 37 ifTrue: [
  470. self previousFocus ] ]
  471. ! !
  472. HLWidget subclass: #HLManager
  473. instanceVariableNames: 'tabs activeTab keyBinder environment history'
  474. package: 'Helios-Core'!
  475. !HLManager methodsFor: 'accessing'!
  476. activeTab
  477. ^ activeTab
  478. !
  479. environment
  480. "The default environment used by all Helios objects"
  481. ^ environment ifNil: [ environment := self defaultEnvironment ]
  482. !
  483. environment: anEnvironment
  484. environment := anEnvironment
  485. !
  486. history
  487. ^ history ifNil: [ history := OrderedCollection new ]
  488. !
  489. history: aCollection
  490. history := aCollection
  491. !
  492. keyBinder
  493. ^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]
  494. !
  495. tabs
  496. ^ tabs ifNil: [ tabs := OrderedCollection new ]
  497. ! !
  498. !HLManager methodsFor: 'actions'!
  499. activate: aTab
  500. self keyBinder flushBindings.
  501. aTab registerBindings.
  502. activeTab := aTab.
  503. self
  504. refresh;
  505. addToHistory: aTab;
  506. show: aTab
  507. !
  508. addTab: aTab
  509. self tabs add: aTab.
  510. self activate: aTab
  511. !
  512. addToHistory: aTab
  513. self removeFromHistory: aTab.
  514. self history add: aTab
  515. !
  516. confirm: aString ifFalse: aBlock
  517. (HLConfirmation new
  518. confirmationString: aString;
  519. cancelBlock: aBlock;
  520. yourself)
  521. appendToJQuery: 'body' asJQuery
  522. !
  523. confirm: aString ifTrue: aBlock
  524. (HLConfirmation new
  525. confirmationString: aString;
  526. actionBlock: aBlock;
  527. yourself)
  528. appendToJQuery: 'body' asJQuery
  529. !
  530. removeActiveTab
  531. self removeTab: self activeTab
  532. !
  533. removeFromHistory: aTab
  534. self history: (self history reject: [ :each | each == aTab ])
  535. !
  536. removeTab: aTab
  537. (self tabs includes: aTab) ifFalse: [ ^ self ].
  538. self removeFromHistory: aTab.
  539. self tabs remove: aTab.
  540. self keyBinder flushBindings.
  541. aTab remove.
  542. self refresh.
  543. self history ifNotEmpty: [
  544. self history last activate ]
  545. ! !
  546. !HLManager methodsFor: 'defaults'!
  547. defaultEnvironment
  548. "If helios is loaded from within a frame, answer the parent window environment"
  549. window parent ifNil: [ ^ Environment new ].
  550. ^ ((window parent at: 'smalltalk')
  551. at: 'Environment') new
  552. ! !
  553. !HLManager methodsFor: 'initialization'!
  554. initialize
  555. super initialize.
  556. self keyBinder setupEvents
  557. ! !
  558. !HLManager methodsFor: 'rendering'!
  559. refresh
  560. (window jQuery: '.navbar') remove.
  561. self appendToJQuery: 'body' asJQuery
  562. !
  563. renderAddOn: html
  564. html li
  565. class: 'dropdown';
  566. with: [
  567. html a
  568. class: 'dropdown-toggle';
  569. at: 'data-toggle' put: 'dropdown';
  570. with: [
  571. html with: 'Open...'.
  572. (html tag: 'b') class: 'caret' ].
  573. html ul
  574. class: 'dropdown-menu';
  575. with: [
  576. ((HLWidget withAllSubclasses
  577. select: [ :each | each canBeOpenAsTab ])
  578. sorted: [ :a :b | a tabPriority < b tabPriority ])
  579. do: [ :each |
  580. html li with: [
  581. html a
  582. with: each tabLabel;
  583. onClick: [ each openAsTab ] ] ] ] ]
  584. !
  585. renderContentOn: html
  586. html div
  587. class: 'navbar navbar-fixed-top';
  588. with: [ html div
  589. class: 'navbar-inner';
  590. with: [ self renderTabsOn: html ] ]
  591. !
  592. renderTabsOn: html
  593. html ul
  594. class: 'nav';
  595. with: [
  596. self tabs do: [ :each |
  597. html li
  598. class: (each isActive ifTrue: [ 'active' ] ifFalse: [ 'inactive' ]);
  599. with: [
  600. html a
  601. with: [
  602. ((html tag: 'i') class: 'icon-remove')
  603. onClick: [ self removeTab: each ].
  604. html with: each displayLabel ];
  605. onClick: [ each activate ] ] ].
  606. self renderAddOn: html ]
  607. !
  608. show: aTab
  609. self tabs do: [ :each | each hide ].
  610. aTab show; focus
  611. ! !
  612. HLManager class instanceVariableNames: 'current'!
  613. !HLManager class methodsFor: 'accessing'!
  614. current
  615. ^ current ifNil: [ current := self basicNew initialize ]
  616. ! !
  617. !HLManager class methodsFor: 'initialization'!
  618. initialize
  619. self current appendToJQuery: 'body' asJQuery
  620. ! !
  621. !HLManager class methodsFor: 'instance creation'!
  622. new
  623. "Use current instead"
  624. self shouldNotImplement
  625. ! !
  626. HLWidget subclass: #HLSUnit
  627. instanceVariableNames: ''
  628. package: 'Helios-Core'!
  629. !HLSUnit class methodsFor: 'accessing'!
  630. tabLabel
  631. ^ 'SUnit'
  632. !
  633. tabPriority
  634. ^ 1000
  635. ! !
  636. !HLSUnit class methodsFor: 'testing'!
  637. canBeOpenAsTab
  638. ^ true
  639. ! !
  640. HLWidget subclass: #HLTranscript
  641. instanceVariableNames: ''
  642. package: 'Helios-Core'!
  643. !HLTranscript class methodsFor: 'accessing'!
  644. tabLabel
  645. ^ 'Transcript'
  646. !
  647. tabPriority
  648. ^ 600
  649. ! !
  650. !HLTranscript class methodsFor: 'testing'!
  651. canBeOpenAsTab
  652. ^ true
  653. ! !