Helios-Core.st 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493
  1. Smalltalk current createPackage: 'Helios-Core'!
  2. Object subclass: #HLModel
  3. instanceVariableNames: 'announcer environment'
  4. package: 'Helios-Core'!
  5. !HLModel commentStamp!
  6. I am the abstract superclass of all models of Helios.
  7. I am the "Model" part of the MVC pattern implementation in Helios.
  8. I provide access to an `Environment` object and both a local (model-specific) and global (system-specific) announcer.
  9. The `#withChangesDo:` method is handy for performing model changes ensuring that all widgets are aware of the change and can prevent it from happening.
  10. Modifications of the system should be done via commands (see `HLCommand` and subclasses).!
  11. !HLModel methodsFor: 'accessing'!
  12. announcer
  13. ^ announcer ifNil: [ announcer := Announcer new ]
  14. !
  15. environment
  16. ^ environment ifNil: [ self manager environment ]
  17. !
  18. environment: anEnvironment
  19. environment := anEnvironment
  20. !
  21. manager
  22. ^ HLManager current
  23. !
  24. systemAnnouncer
  25. ^ self environment systemAnnouncer
  26. ! !
  27. !HLModel methodsFor: 'error handling'!
  28. withChangesDo: aBlock
  29. [
  30. self announcer announce: (HLAboutToChange new
  31. actionBlock: aBlock).
  32. aBlock value.
  33. ]
  34. on: HLChangeForbidden
  35. do: [ :ex | ]
  36. ! !
  37. !HLModel methodsFor: 'testing'!
  38. isBrowserModel
  39. ^ false
  40. !
  41. isReferencesModel
  42. ^ false
  43. !
  44. isToolModel
  45. ^ false
  46. ! !
  47. HLModel subclass: #HLToolModel
  48. instanceVariableNames: 'selectedClass selectedPackage selectedProtocol selectedSelector'
  49. package: 'Helios-Core'!
  50. !HLToolModel commentStamp!
  51. I am a model specific to package and class manipulation. All browsers should either use me or a subclass as their model.
  52. I provide methods for package, class, protocol and method manipulation and access, forwarding to my environment.
  53. I also handle compilation of classes and methods as well as compilation and parsing errors.!
  54. !HLToolModel methodsFor: 'accessing'!
  55. allSelectors
  56. ^ self environment allSelectors
  57. !
  58. availableClassNames
  59. ^ self environment availableClassNames
  60. !
  61. availablePackageNames
  62. ^ self environment availablePackageNames
  63. !
  64. availablePackages
  65. ^ self environment availablePackageNames
  66. !
  67. availableProtocols
  68. ^ self environment availableProtocolsFor: self selectedClass
  69. !
  70. packages
  71. ^ self environment packages
  72. !
  73. selectedClass
  74. ^ selectedClass
  75. !
  76. selectedClass: aClass
  77. (self selectedClass = aClass and: [ aClass isNil ])
  78. ifTrue: [ ^ self ].
  79. self withChangesDo: [
  80. selectedClass = aClass ifTrue: [
  81. self selectedProtocol: nil ].
  82. aClass
  83. ifNil: [ selectedClass := nil ]
  84. ifNotNil: [
  85. self selectedPackage: aClass theNonMetaClass package.
  86. self showInstance
  87. ifTrue: [ selectedClass := aClass theNonMetaClass ]
  88. ifFalse: [ selectedClass := aClass theMetaClass ] ].
  89. self selectedProtocol: nil.
  90. self announcer announce: (HLClassSelected on: self selectedClass) ]
  91. !
  92. selectedMethod
  93. ^ self selectedClass ifNotNil: [
  94. self selectedClass methodDictionary
  95. at: selectedSelector
  96. ifAbsent: [ nil ] ]
  97. !
  98. selectedMethod: aCompiledMethod
  99. selectedSelector = aCompiledMethod ifTrue: [ ^ self ].
  100. self withChangesDo: [
  101. aCompiledMethod
  102. ifNil: [ selectedSelector := nil ]
  103. ifNotNil: [
  104. selectedClass := aCompiledMethod methodClass.
  105. selectedSelector := aCompiledMethod selector ].
  106. self announcer announce: (HLMethodSelected on: aCompiledMethod) ]
  107. !
  108. selectedPackage
  109. ^ selectedPackage
  110. !
  111. selectedPackage: aPackage
  112. selectedPackage = aPackage ifTrue: [ ^ self ].
  113. self withChangesDo: [
  114. selectedPackage := aPackage.
  115. self selectedClass: nil.
  116. self announcer announce: (HLPackageSelected on: aPackage) ]
  117. !
  118. selectedProtocol
  119. ^ selectedProtocol
  120. !
  121. selectedProtocol: aString
  122. selectedProtocol = aString ifTrue: [ ^ self ].
  123. self withChangesDo: [
  124. selectedProtocol := aString.
  125. self selectedMethod: nil.
  126. self announcer announce: (HLProtocolSelected on: aString) ]
  127. ! !
  128. !HLToolModel methodsFor: 'actions'!
  129. addInstVarNamed: aString
  130. self environment addInstVarNamed: aString to: self selectedClass.
  131. self announcer announce: (HLInstVarAdded new
  132. theClass: self selectedClass;
  133. variableName: aString;
  134. yourself)
  135. !
  136. save: aString
  137. self announcer announce: HLSourceCodeSaved new.
  138. (self shouldCompileClassDefinition: aString)
  139. ifTrue: [ self compileClassDefinition: aString ]
  140. ifFalse: [ self compileMethod: aString ]
  141. !
  142. saveSourceCode
  143. self announcer announce: HLSaveSourceCode new
  144. ! !
  145. !HLToolModel methodsFor: 'commands actions'!
  146. commitPackage
  147. self
  148. withHelperLabelled: 'Committing package ', self selectedPackage name, '...'
  149. do: [ self environment commitPackage: self selectedPackage ]
  150. !
  151. copyClassTo: aClassName
  152. self withChangesDo: [
  153. self environment
  154. copyClass: self selectedClass theNonMetaClass
  155. to: aClassName ]
  156. !
  157. moveClassToPackage: aPackageName
  158. self withChangesDo: [
  159. self environment
  160. moveClass: self selectedClass theNonMetaClass
  161. toPackage: aPackageName ]
  162. !
  163. moveMethodToClass: aClassName
  164. self withChangesDo: [
  165. self environment
  166. moveMethod: self selectedMethod
  167. toClass: aClassName ]
  168. !
  169. moveMethodToProtocol: aProtocol
  170. self withChangesDo: [
  171. self environment
  172. moveMethod: self selectedMethod
  173. toProtocol: aProtocol ]
  174. !
  175. openClassNamed: aString
  176. | class |
  177. self withChangesDo: [
  178. class := self environment classNamed: aString.
  179. self selectedPackage: class package.
  180. self selectedClass: class ]
  181. !
  182. removeClass
  183. self withChangesDo: [
  184. self manager
  185. confirm: 'Do you REALLY want to remove class ', self selectedClass name
  186. ifTrue: [ self environment removeClass: self selectedClass ] ]
  187. !
  188. removeMethod
  189. self withChangesDo: [
  190. self manager
  191. confirm: 'Do you REALLY want to remove method ', self selectedMethod methodClass name,' >> #', self selectedMethod selector
  192. ifTrue: [ self environment removeMethod: self selectedMethod ] ]
  193. !
  194. removeProtocol
  195. self withChangesDo: [
  196. self manager
  197. confirm: 'Do you REALLY want to remove protocol ', self selectedProtocol
  198. ifTrue: [ self environment
  199. removeProtocol: self selectedProtocol
  200. from: self selectedClass ] ]
  201. !
  202. renameClassTo: aClassName
  203. self withChangesDo: [
  204. self environment
  205. renameClass: self selectedClass theNonMetaClass
  206. to: aClassName ]
  207. !
  208. renameProtocolTo: aString
  209. self withChangesDo: [
  210. self environment
  211. renameProtocol: self selectedProtocol
  212. to: aString
  213. in: self selectedClass ]
  214. ! !
  215. !HLToolModel methodsFor: 'compiling'!
  216. compileClassComment: aString
  217. self environment
  218. compileClassComment: aString
  219. for: self selectedClass
  220. !
  221. compileClassDefinition: aString
  222. self environment compileClassDefinition: aString
  223. !
  224. compileMethod: aString
  225. | method |
  226. self withCompileErrorHandling: [
  227. method := self environment
  228. compileMethod: aString
  229. for: self selectedClass
  230. protocol: self compilationProtocol.
  231. self selectedMethod: method ]
  232. ! !
  233. !HLToolModel methodsFor: 'defaults'!
  234. allProtocol
  235. ^ '-- all --'
  236. !
  237. unclassifiedProtocol
  238. ^ 'as yet unclassified'
  239. ! !
  240. !HLToolModel methodsFor: 'error handling'!
  241. handleCompileError: anError
  242. self announcer announce: (HLCompileErrorRaised new
  243. error: anError;
  244. yourself)
  245. !
  246. handleParseError: anError
  247. | split line column messageToInsert |
  248. split := anError messageText tokenize: ' : '.
  249. messageToInsert := split second.
  250. "21 = 'Parse error on line ' size + 1"
  251. split := split first copyFrom: 21 to: split first size.
  252. split := split tokenize: ' column '.
  253. line := split first.
  254. column := split second.
  255. self announcer announce: (HLParseErrorRaised new
  256. line: line asNumber;
  257. column: column asNumber;
  258. message: messageToInsert;
  259. error: anError;
  260. yourself)
  261. !
  262. handleUnkownVariableError: anError
  263. self announcer announce: (HLUnknownVariableErrorRaised new
  264. error: anError;
  265. yourself)
  266. !
  267. withCompileErrorHandling: aBlock
  268. [
  269. [
  270. aBlock
  271. on: ParseError
  272. do: [:ex | self handleParseError: ex ]
  273. ]
  274. on: UnknownVariableError
  275. do: [ :ex | self handleUnkownVariableError: ex ]
  276. ]
  277. on: CompilerError
  278. do: [ :ex | self handleCompileError: ex ]
  279. ! !
  280. !HLToolModel methodsFor: 'private'!
  281. compilationProtocol
  282. | currentProtocol |
  283. currentProtocol := self selectedProtocol.
  284. currentProtocol ifNil: [ currentProtocol := self unclassifiedProtocol ].
  285. self selectedMethod ifNotNil: [ currentProtocol := self selectedMethod protocol ].
  286. ^ currentProtocol = self allProtocol
  287. ifTrue: [ self unclassifiedProtocol ]
  288. ifFalse: [ currentProtocol ]
  289. !
  290. withHelperLabelled: aString do: aBlock
  291. "TODO: doesn't belong here"
  292. (window jQuery: '#helper') remove.
  293. [ :html |
  294. html div
  295. id: 'helper';
  296. with: aString ] appendToJQuery: 'body' asJQuery.
  297. [
  298. aBlock value.
  299. (window jQuery: '#helper') remove
  300. ]
  301. valueWithTimeout: 10
  302. ! !
  303. !HLToolModel methodsFor: 'testing'!
  304. isToolModel
  305. ^ true
  306. !
  307. shouldCompileClassDefinition: aString
  308. ^ self selectedClass isNil or: [
  309. aString match: '^[A-Z]' ]
  310. ! !
  311. !HLToolModel class methodsFor: 'actions'!
  312. on: anEnvironment
  313. ^ self new
  314. environment: anEnvironment;
  315. yourself
  316. ! !
  317. Widget subclass: #HLTab
  318. instanceVariableNames: 'widget label root'
  319. package: 'Helios-Core'!
  320. !HLTab commentStamp!
  321. I am a widget specialized into building another widget as an Helios tab.
  322. I should not be used directly, `HLWidget class >> #openAsTab` should be used instead.
  323. ## Example
  324. HLWorkspace openAsTab!
  325. !HLTab methodsFor: 'accessing'!
  326. activate
  327. self manager activate: self
  328. !
  329. add
  330. self manager addTab: self
  331. !
  332. cssClass
  333. ^ self widget tabClass
  334. !
  335. displayLabel
  336. ^ self label size > 20
  337. ifTrue: [ (self label first: 20), '...' ]
  338. ifFalse: [ self label ]
  339. !
  340. focus
  341. self widget canHaveFocus ifTrue: [
  342. self widget focus ]
  343. !
  344. label
  345. ^ label ifNil: [ '' ]
  346. !
  347. label: aString
  348. label := aString
  349. !
  350. manager
  351. ^ HLManager current
  352. !
  353. widget
  354. ^ widget
  355. !
  356. widget: aWidget
  357. widget := aWidget
  358. ! !
  359. !HLTab methodsFor: 'actions'!
  360. hide
  361. root ifNotNil: [ root asJQuery css: 'visibility' put: 'hidden' ]
  362. !
  363. registerBindings
  364. self widget registerBindings
  365. !
  366. remove
  367. self widget unregister.
  368. root ifNotNil: [ root asJQuery remove ]
  369. !
  370. show
  371. root
  372. ifNil: [ self appendToJQuery: 'body' asJQuery ]
  373. ifNotNil: [ root asJQuery css: 'visibility' put: 'visible' ]
  374. ! !
  375. !HLTab methodsFor: 'rendering'!
  376. renderOn: html
  377. root := html div
  378. class: 'tab';
  379. yourself.
  380. self renderTab
  381. !
  382. renderTab
  383. root contents: [ :html |
  384. html div
  385. class: 'amber_box';
  386. with: [ self widget renderOn: html ] ]
  387. ! !
  388. !HLTab methodsFor: 'testing'!
  389. isActive
  390. ^ self manager activeTab = self
  391. ! !
  392. !HLTab class methodsFor: 'instance creation'!
  393. on: aWidget labelled: aString
  394. ^ self new
  395. widget: aWidget;
  396. label: aString;
  397. yourself
  398. ! !
  399. Widget subclass: #HLWidget
  400. instanceVariableNames: 'wrapper'
  401. package: 'Helios-Core'!
  402. !HLWidget commentStamp!
  403. I am the abstract superclass of all Helios widgets.
  404. I provide common methods, additional behavior to widgets useful for Helios, like dialog creation, command execution and tab creation.
  405. ## API
  406. 1. Rendering
  407. Instead of overriding `#renderOn:` as with other Widget subclasses, my subclasses should override `#renderContentOn:`.
  408. 2. Refreshing
  409. To re-render a widget, use `#refresh`.
  410. 3. Key bindings registration and tabs
  411. When displayed as a tab, the widget has a chance to register keybindings with the `#registerBindingsOn:` hook method.
  412. 4. Unregistration
  413. When a widget has subscribed to announcements or other actions that need to be cleared when closing the tab, the hook method `#unregister` will be called by helios.
  414. 5. Tabs
  415. To enable a widget class to be open as a tab, override the class-side `#canBeOpenAsTab` method to answer `true`. `#tabClass` and `#tabPriority` can be overridden too to respectively change the css class of the tab and the order of tabs in the main menu.
  416. 6. Command execution
  417. An helios command (instance of `HLCommand` or one of its subclass) can be executed with `#execute:`.!
  418. !HLWidget methodsFor: 'accessing'!
  419. manager
  420. ^ HLManager current
  421. !
  422. tabClass
  423. ^ self class tabClass
  424. !
  425. wrapper
  426. ^ wrapper
  427. ! !
  428. !HLWidget methodsFor: 'actions'!
  429. alert: aString
  430. window alert: aString
  431. !
  432. confirm: aString ifTrue: aBlock
  433. self manager confirm: aString ifTrue: aBlock
  434. !
  435. execute: aCommand
  436. HLManager current keyBinder
  437. activate;
  438. applyBinding: aCommand asBinding
  439. !
  440. request: aString do: aBlock
  441. self manager request: aString do: aBlock
  442. !
  443. request: aString value: valueString do: aBlock
  444. self manager
  445. request: aString
  446. value: valueString
  447. do: aBlock
  448. !
  449. unregister
  450. "This method is called whenever the receiver is closed (as a tab).
  451. Widgets subscribing to announcements should unregister there"
  452. ! !
  453. !HLWidget methodsFor: 'keybindings'!
  454. registerBindings
  455. self registerBindingsOn: self manager keyBinder bindings
  456. !
  457. registerBindingsOn: aBindingGroup
  458. ! !
  459. !HLWidget methodsFor: 'rendering'!
  460. renderContentOn: html
  461. !
  462. renderOn: html
  463. wrapper := html div.
  464. [ :renderer | self renderContentOn: renderer ] appendToJQuery: wrapper asJQuery
  465. ! !
  466. !HLWidget methodsFor: 'testing'!
  467. canHaveFocus
  468. ^ false
  469. ! !
  470. !HLWidget methodsFor: 'updating'!
  471. refresh
  472. self wrapper ifNil: [ ^ self ].
  473. self wrapper asJQuery empty.
  474. [ :html | self renderContentOn: html ] appendToJQuery: self wrapper asJQuery
  475. ! !
  476. !HLWidget class methodsFor: 'accessing'!
  477. openAsTab
  478. self canBeOpenAsTab ifFalse: [ ^ self ].
  479. HLManager current addTab: (HLTab on: self new labelled: self tabLabel)
  480. !
  481. tabClass
  482. ^ ''
  483. !
  484. tabLabel
  485. ^ 'Tab'
  486. !
  487. tabPriority
  488. ^ 500
  489. ! !
  490. !HLWidget class methodsFor: 'testing'!
  491. canBeOpenAsTab
  492. ^ false
  493. ! !
  494. HLWidget subclass: #HLConfirmation
  495. instanceVariableNames: 'confirmationString actionBlock cancelBlock'
  496. package: 'Helios-Core'!
  497. !HLConfirmation commentStamp!
  498. I display confirmation messages.
  499. Instead of creating an instance directly, use `HLWidget >> #confirm:ifTrue:`.!
  500. !HLConfirmation methodsFor: 'accessing'!
  501. actionBlock
  502. ^ actionBlock ifNil: [ [] ]
  503. !
  504. actionBlock: aBlock
  505. actionBlock := aBlock
  506. !
  507. cancelBlock
  508. ^ cancelBlock ifNil: [ [] ]
  509. !
  510. cancelBlock: aBlock
  511. cancelBlock := aBlock
  512. !
  513. confirmationString
  514. ^ confirmationString ifNil: [ 'Confirm' ]
  515. !
  516. confirmationString: aString
  517. confirmationString := aString
  518. !
  519. cssClass
  520. ^ ''
  521. ! !
  522. !HLConfirmation methodsFor: 'actions'!
  523. cancel
  524. self cancelBlock value.
  525. self remove
  526. !
  527. confirm
  528. self actionBlock value.
  529. self remove
  530. !
  531. remove
  532. (window jQuery: '.dialog') removeClass: 'active'.
  533. [
  534. (window jQuery: '#overlay') remove.
  535. (window jQuery: '.dialog') remove
  536. ] valueWithTimeout: 300
  537. ! !
  538. !HLConfirmation methodsFor: 'rendering'!
  539. renderButtonsOn: html
  540. | confirmButton |
  541. html div
  542. class: 'buttons';
  543. with: [
  544. html button
  545. class: 'button';
  546. with: 'Cancel';
  547. onClick: [ self cancel ].
  548. confirmButton := html button
  549. class: 'button default';
  550. with: 'Confirm';
  551. onClick: [ self confirm ] ].
  552. confirmButton asJQuery focus
  553. !
  554. renderContentOn: html
  555. | confirmButton |
  556. html div id: 'overlay'.
  557. html div
  558. class: 'dialog ', self cssClass;
  559. with: [
  560. self
  561. renderMainOn: html;
  562. renderButtonsOn: html ].
  563. (window jQuery: '.dialog') addClass: 'active'.
  564. self setupKeyBindings
  565. !
  566. renderMainOn: html
  567. html span with: self confirmationString
  568. !
  569. setupKeyBindings
  570. (window jQuery: '.dialog') keyup: [ :e |
  571. e keyCode = 27 ifTrue: [ self cancel ] ]
  572. ! !
  573. HLConfirmation subclass: #HLRequest
  574. instanceVariableNames: 'input value'
  575. package: 'Helios-Core'!
  576. !HLRequest commentStamp!
  577. I display a modal window requesting user input.
  578. Instead of creating instances manually, use `HLWidget >> #request:do:` and `#request:value:do:`.!
  579. !HLRequest methodsFor: 'accessing'!
  580. cssClass
  581. ^ 'large'
  582. !
  583. value
  584. ^ value ifNil: [ '' ]
  585. !
  586. value: aString
  587. value := aString
  588. ! !
  589. !HLRequest methodsFor: 'actions'!
  590. confirm
  591. self actionBlock value: input asJQuery val.
  592. self remove
  593. ! !
  594. !HLRequest methodsFor: 'rendering'!
  595. renderMainOn: html
  596. super renderMainOn: html.
  597. input := html textarea.
  598. input asJQuery val: self value
  599. ! !
  600. HLWidget subclass: #HLFocusableWidget
  601. instanceVariableNames: ''
  602. package: 'Helios-Core'!
  603. !HLFocusableWidget commentStamp!
  604. I am a widget that can be focused.
  605. ## API
  606. Instead of overriding `#renderOn:` as with other `Widget` subclasses, my subclasses should override `#renderContentOn:`.
  607. To bring the focus to the widget, use the `#focus` method.!
  608. !HLFocusableWidget methodsFor: 'accessing'!
  609. focusClass
  610. ^ 'focused'
  611. ! !
  612. !HLFocusableWidget methodsFor: 'events'!
  613. blur
  614. self wrapper asJQuery blur
  615. !
  616. focus
  617. self wrapper asJQuery focus
  618. !
  619. hasFocus
  620. ^ self wrapper notNil and: [ self wrapper asJQuery is: ':focus' ]
  621. ! !
  622. !HLFocusableWidget methodsFor: 'rendering'!
  623. renderContentOn: html
  624. !
  625. renderOn: html
  626. wrapper := html div
  627. class: 'hl_widget';
  628. yourself.
  629. wrapper with: [ self renderContentOn: html ].
  630. wrapper
  631. at: 'tabindex' put: '0';
  632. onBlur: [ self wrapper asJQuery removeClass: self focusClass ];
  633. onFocus: [ self wrapper asJQuery addClass: self focusClass ]
  634. ! !
  635. !HLFocusableWidget methodsFor: 'testing'!
  636. canHaveFocus
  637. ^ true
  638. ! !
  639. HLFocusableWidget subclass: #HLListWidget
  640. instanceVariableNames: 'items selectedItem mapping'
  641. package: 'Helios-Core'!
  642. !HLListWidget methodsFor: 'accessing'!
  643. cssClassForItem: anObject
  644. ^ ''
  645. !
  646. items
  647. ^ items ifNil: [ items := self defaultItems ]
  648. !
  649. items: aCollection
  650. items := aCollection
  651. !
  652. listCssClassForItem: anObject
  653. ^ self selectedItem = anObject
  654. ifTrue: [ 'active' ]
  655. ifFalse: [ 'inactive' ]
  656. !
  657. positionOf: aListItem
  658. <
  659. return aListItem.parent().children().get().indexOf(aListItem.get(0)) + 1
  660. >
  661. !
  662. selectedItem
  663. ^ selectedItem
  664. !
  665. selectedItem: anObject
  666. selectedItem := anObject
  667. ! !
  668. !HLListWidget methodsFor: 'actions'!
  669. activateFirstListItem
  670. self activateListItem: (window jQuery: ((wrapper asJQuery find: 'li.inactive') get: 0))
  671. !
  672. activateItem: anObject
  673. self activateListItem: (mapping
  674. at: anObject
  675. ifAbsent: [ ^ self ]) asJQuery
  676. !
  677. activateListItem: aListItem
  678. | item |
  679. (aListItem get: 0) ifNil: [ ^self ].
  680. aListItem parent children removeClass: 'active'.
  681. aListItem addClass: 'active'.
  682. self ensureVisible: aListItem.
  683. "Activate the corresponding item"
  684. item := (self items at: (aListItem attr: 'list-data') asNumber).
  685. self selectedItem == item ifFalse: [
  686. self selectItem: item ]
  687. !
  688. activateNextListItem
  689. self activateListItem: (self wrapper asJQuery find: 'li.active') next.
  690. "select the first item if none is selected"
  691. (self wrapper asJQuery find: ' .active') get ifEmpty: [
  692. self activateFirstListItem ]
  693. !
  694. activatePreviousListItem
  695. self activateListItem: (self wrapper asJQuery find: 'li.active') prev
  696. !
  697. ensureVisible: aListItem
  698. "Move the scrollbar to show the active element"
  699. | perent position |
  700. position := self positionOf: aListItem.
  701. parent := aListItem parent.
  702. aListItem position top < 0 ifTrue: [
  703. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].
  704. aListItem position top + aListItem height > parent height ifTrue: [
  705. (parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ]
  706. !
  707. focus
  708. super focus.
  709. self items isEmpty ifFalse: [
  710. self selectedItem ifNil: [ self activateFirstListItem ] ]
  711. !
  712. refresh
  713. super refresh.
  714. self ensureVisible: (mapping
  715. at: self selectedItem
  716. ifAbsent: [ ^ self ]) asJQuery
  717. !
  718. selectItem: anObject
  719. self selectedItem: anObject
  720. ! !
  721. !HLListWidget methodsFor: 'defaults'!
  722. defaultItems
  723. ^ #()
  724. ! !
  725. !HLListWidget methodsFor: 'events'!
  726. setupKeyBindings
  727. "TODO: refactor this!!"
  728. | active interval delay repeatInterval |
  729. active := false.
  730. repeatInterval := 70.
  731. self wrapper asJQuery unbind: 'keydown'.
  732. self wrapper asJQuery keydown: [ :e |
  733. (e which = 38 and: [ active = false ]) ifTrue: [
  734. active := true.
  735. self activatePreviousListItem.
  736. delay := [
  737. interval := [
  738. (self wrapper asJQuery hasClass: self focusClass)
  739. ifTrue: [
  740. self activatePreviousListItem ]
  741. ifFalse: [
  742. active := false.
  743. interval ifNotNil: [ interval clearInterval ].
  744. delay ifNotNil: [ delay clearTimeout] ] ]
  745. valueWithInterval: repeatInterval ]
  746. valueWithTimeout: 300 ].
  747. (e which = 40 and: [ active = false ]) ifTrue: [
  748. active := true.
  749. self activateNextListItem.
  750. delay := [
  751. interval := [
  752. (self wrapper asJQuery hasClass: self focusClass)
  753. ifTrue: [
  754. self activateNextListItem ]
  755. ifFalse: [
  756. active := false.
  757. interval ifNotNil: [ interval clearInterval ].
  758. delay ifNotNil: [ delay clearTimeout] ] ]
  759. valueWithInterval: repeatInterval ]
  760. valueWithTimeout: 300 ] ].
  761. self wrapper asJQuery keyup: [ :e |
  762. active ifTrue: [
  763. active := false.
  764. interval ifNotNil: [ interval clearInterval ].
  765. delay ifNotNil: [ delay clearTimeout] ] ]
  766. ! !
  767. !HLListWidget methodsFor: 'initialization'!
  768. initialize
  769. super initialize.
  770. mapping := Dictionary new.
  771. ! !
  772. !HLListWidget methodsFor: 'private'!
  773. registerMappingFrom: anObject to: aTag
  774. mapping at: anObject put: aTag
  775. ! !
  776. !HLListWidget methodsFor: 'rendering'!
  777. renderButtonsOn: html
  778. !
  779. renderContentOn: html
  780. html ul
  781. class: 'nav nav-pills nav-stacked';
  782. with: [ self renderListOn: html ].
  783. html div class: 'pane_actions form-actions'; with: [
  784. self renderButtonsOn: html ].
  785. self setupKeyBindings
  786. !
  787. renderItem: anObject on: html
  788. | li |
  789. li := html li.
  790. self registerMappingFrom: anObject to: li.
  791. li
  792. at: 'list-data' put: (self items indexOf: anObject) asString;
  793. class: (self listCssClassForItem: anObject);
  794. with: [
  795. html a
  796. with: [
  797. (html tag: 'i') class: (self cssClassForItem: anObject).
  798. self renderItemLabel: anObject on: html ];
  799. onClick: [
  800. self activateListItem: li asJQuery ] ]
  801. !
  802. renderItemLabel: anObject on: html
  803. html with: anObject asString
  804. !
  805. renderListOn: html
  806. mapping := Dictionary new.
  807. self items do: [ :each |
  808. self renderItem: each on: html ]
  809. ! !
  810. HLListWidget subclass: #HLNavigationListWidget
  811. instanceVariableNames: 'previous next'
  812. package: 'Helios-Core'!
  813. !HLNavigationListWidget methodsFor: 'accessing'!
  814. next
  815. ^ next
  816. !
  817. next: aWidget
  818. next := aWidget.
  819. aWidget previous = self ifFalse: [ aWidget previous: self ]
  820. !
  821. previous
  822. ^ previous
  823. !
  824. previous: aWidget
  825. previous := aWidget.
  826. aWidget next = self ifFalse: [ aWidget next: self ]
  827. ! !
  828. !HLNavigationListWidget methodsFor: 'actions'!
  829. nextFocus
  830. self next ifNotNil: [ self next focus ]
  831. !
  832. previousFocus
  833. self previous ifNotNil: [ self previous focus ]
  834. ! !
  835. !HLNavigationListWidget methodsFor: 'events'!
  836. setupKeyBindings
  837. super setupKeyBindings.
  838. self wrapper asJQuery keydown: [ :e |
  839. e which = 39 ifTrue: [
  840. self nextFocus ].
  841. e which = 37 ifTrue: [
  842. self previousFocus ] ]
  843. ! !
  844. HLNavigationListWidget subclass: #HLToolListWidget
  845. instanceVariableNames: 'model'
  846. package: 'Helios-Core'!
  847. !HLToolListWidget methodsFor: 'accessing'!
  848. commandCategory
  849. ^ self label
  850. !
  851. label
  852. ^ 'List'
  853. !
  854. menuCommands
  855. "Answer a collection of commands to be put in the cog menu"
  856. ^ ((HLToolCommand concreteClasses
  857. select: [ :each | each isValidFor: self model ])
  858. collect: [ :each | each for: self model ])
  859. select: [ :each |
  860. each category = self commandCategory and: [
  861. each isAction and: [ each isActive ] ] ]
  862. !
  863. model
  864. ^ model
  865. !
  866. model: aBrowserModel
  867. model := aBrowserModel.
  868. self
  869. observeSystem;
  870. observeModel
  871. !
  872. selectedItem: anItem
  873. "Selection changed, update the cog menu"
  874. super selectedItem: anItem.
  875. self updateMenu
  876. ! !
  877. !HLToolListWidget methodsFor: 'actions'!
  878. activateListItem: anItem
  879. self model withChangesDo: [ super activateListItem: anItem ]
  880. !
  881. activateNextListItem
  882. self model withChangesDo: [ super activateNextListItem ]
  883. !
  884. activatePreviousListItem
  885. self model withChangesDo: [ super activatePreviousListItem ]
  886. !
  887. observeModel
  888. !
  889. observeSystem
  890. !
  891. unregister
  892. super unregister.
  893. self model announcer unsubscribe: self.
  894. self model systemAnnouncer unsubscribe: self
  895. ! !
  896. !HLToolListWidget methodsFor: 'rendering'!
  897. renderContentOn: html
  898. self renderHeadOn: html.
  899. super renderContentOn: html
  900. !
  901. renderHeadOn: html
  902. html div
  903. class: 'list-label';
  904. with: [
  905. html with: self label.
  906. self renderMenuOn: html ]
  907. !
  908. renderMenuOn: html
  909. | commands |
  910. commands := self menuCommands.
  911. commands isEmpty ifTrue: [ ^ self ].
  912. html div
  913. class: 'btn-group cog';
  914. with: [
  915. html a
  916. class: 'btn dropdown-toggle';
  917. at: 'data-toggle' put: 'dropdown';
  918. with: [ (html tag: 'i') class: 'icon-cog' ].
  919. html ul
  920. class: 'dropdown-menu pull-right';
  921. with: [
  922. self menuCommands do: [ :each |
  923. html li with: [ html a
  924. with: each menuLabel;
  925. onClick: [ self execute: each ] ] ] ] ]
  926. ! !
  927. !HLToolListWidget methodsFor: 'updating'!
  928. updateMenu
  929. (self wrapper asJQuery find: '.cog') remove.
  930. [ :html | self renderMenuOn: html ]
  931. appendToJQuery: (self wrapper asJQuery find: '.list-label')
  932. ! !
  933. !HLToolListWidget class methodsFor: 'instance creation'!
  934. on: aModel
  935. ^ self new
  936. model: aModel;
  937. yourself
  938. ! !
  939. HLWidget subclass: #HLManager
  940. instanceVariableNames: 'tabs activeTab keyBinder environment history'
  941. package: 'Helios-Core'!
  942. !HLManager methodsFor: 'accessing'!
  943. activeTab
  944. ^ activeTab
  945. !
  946. environment
  947. "The default environment used by all Helios objects"
  948. ^ environment ifNil: [ environment := self defaultEnvironment ]
  949. !
  950. environment: anEnvironment
  951. environment := anEnvironment
  952. !
  953. history
  954. ^ history ifNil: [ history := OrderedCollection new ]
  955. !
  956. history: aCollection
  957. history := aCollection
  958. !
  959. keyBinder
  960. ^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]
  961. !
  962. tabs
  963. ^ tabs ifNil: [ tabs := OrderedCollection new ]
  964. ! !
  965. !HLManager methodsFor: 'actions'!
  966. activate: aTab
  967. self keyBinder flushBindings.
  968. aTab registerBindings.
  969. activeTab := aTab.
  970. self
  971. refresh;
  972. addToHistory: aTab;
  973. show: aTab
  974. !
  975. addTab: aTab
  976. self tabs add: aTab.
  977. self activate: aTab
  978. !
  979. addToHistory: aTab
  980. self removeFromHistory: aTab.
  981. self history add: aTab
  982. !
  983. confirm: aString ifFalse: aBlock
  984. (HLConfirmation new
  985. confirmationString: aString;
  986. cancelBlock: aBlock;
  987. yourself)
  988. appendToJQuery: 'body' asJQuery
  989. !
  990. confirm: aString ifTrue: aBlock
  991. (HLConfirmation new
  992. confirmationString: aString;
  993. actionBlock: aBlock;
  994. yourself)
  995. appendToJQuery: 'body' asJQuery
  996. !
  997. registerErrorHandler: anErrorHandler
  998. self environment registerErrorHandler: anErrorHandler
  999. !
  1000. registerInspector: anInspector
  1001. self environment registerInspector: anInspector
  1002. !
  1003. removeActiveTab
  1004. self removeTab: self activeTab
  1005. !
  1006. removeFromHistory: aTab
  1007. self history: (self history reject: [ :each | each == aTab ])
  1008. !
  1009. removeTab: aTab
  1010. (self tabs includes: aTab) ifFalse: [ ^ self ].
  1011. self removeFromHistory: aTab.
  1012. self tabs remove: aTab.
  1013. self keyBinder flushBindings.
  1014. aTab remove.
  1015. self refresh.
  1016. self history ifNotEmpty: [
  1017. self history last activate ]
  1018. !
  1019. request: aString do: aBlock
  1020. self
  1021. request: aString
  1022. value: ''
  1023. do: aBlock
  1024. !
  1025. request: aString value: valueString do: aBlock
  1026. (HLRequest new
  1027. confirmationString: aString;
  1028. actionBlock: aBlock;
  1029. value: valueString;
  1030. yourself)
  1031. appendToJQuery: 'body' asJQuery
  1032. ! !
  1033. !HLManager methodsFor: 'defaults'!
  1034. defaultEnvironment
  1035. "If helios is loaded from within a frame, answer the parent window environment"
  1036. | parent |
  1037. parent := window opener ifNil: [ window parent ].
  1038. parent ifNil: [ ^ Environment new ].
  1039. ^ ((parent at: 'smalltalk')
  1040. at: 'Environment') new
  1041. ! !
  1042. !HLManager methodsFor: 'initialization'!
  1043. initialize
  1044. super initialize.
  1045. self registerInspector: HLInspector.
  1046. self registerErrorHandler: HLErrorHandler.
  1047. self keyBinder setupEvents
  1048. ! !
  1049. !HLManager methodsFor: 'rendering'!
  1050. refresh
  1051. (window jQuery: '.navbar') remove.
  1052. self appendToJQuery: 'body' asJQuery
  1053. !
  1054. renderAddOn: html
  1055. html li
  1056. class: 'dropdown';
  1057. with: [
  1058. html a
  1059. class: 'dropdown-toggle';
  1060. at: 'data-toggle' put: 'dropdown';
  1061. with: [
  1062. html with: 'Open...'.
  1063. (html tag: 'b') class: 'caret' ].
  1064. html ul
  1065. class: 'dropdown-menu';
  1066. with: [
  1067. ((HLWidget withAllSubclasses
  1068. select: [ :each | each canBeOpenAsTab ])
  1069. sorted: [ :a :b | a tabPriority < b tabPriority ])
  1070. do: [ :each |
  1071. html li with: [
  1072. html a
  1073. with: each tabLabel;
  1074. onClick: [ each openAsTab ] ] ] ] ]
  1075. !
  1076. renderContentOn: html
  1077. html div
  1078. class: 'navbar navbar-fixed-top';
  1079. with: [ html div
  1080. class: 'navbar-inner';
  1081. with: [ self renderTabsOn: html ] ]
  1082. !
  1083. renderTabsOn: html
  1084. html ul
  1085. class: 'nav';
  1086. with: [
  1087. self tabs do: [ :each |
  1088. html li
  1089. class: (each isActive ifTrue: [ 'active' ] ifFalse: [ 'inactive' ]);
  1090. with: [
  1091. html a
  1092. with: [
  1093. ((html tag: 'i') class: 'close')
  1094. onClick: [ self removeTab: each ].
  1095. html span
  1096. class: each cssClass;
  1097. with: each displayLabel ];
  1098. onClick: [ each activate ] ] ].
  1099. self renderAddOn: html ]
  1100. !
  1101. show: aTab
  1102. self tabs do: [ :each | each hide ].
  1103. aTab show; focus
  1104. ! !
  1105. HLManager class instanceVariableNames: 'current'!
  1106. !HLManager class methodsFor: 'accessing'!
  1107. current
  1108. ^ current ifNil: [ current := self basicNew initialize ]
  1109. ! !
  1110. !HLManager class methodsFor: 'initialization'!
  1111. initialize
  1112. self current appendToJQuery: 'body' asJQuery
  1113. ! !
  1114. !HLManager class methodsFor: 'instance creation'!
  1115. new
  1116. "Use current instead"
  1117. self shouldNotImplement
  1118. ! !
  1119. HLWidget subclass: #HLSUnit
  1120. instanceVariableNames: ''
  1121. package: 'Helios-Core'!
  1122. !HLSUnit class methodsFor: 'accessing'!
  1123. tabClass
  1124. ^ 'sunit'
  1125. !
  1126. tabLabel
  1127. ^ 'SUnit'
  1128. !
  1129. tabPriority
  1130. ^ 1000
  1131. ! !
  1132. !HLSUnit class methodsFor: 'testing'!
  1133. canBeOpenAsTab
  1134. ^ true
  1135. ! !