From 08bab96d04fa15a7dfa7d87010df2e56e6aa2201 Mon Sep 17 00:00:00 2001 From: Martin Dias Date: Wed, 26 Jun 2024 15:17:42 -0400 Subject: [PATCH] Recovering my ToDoList example for Spec-Toplo repo --- .../SpToDoList.class.st | 85 ++++++ .../SpToDoListPresenter.class.st | 265 ++++++++++++++++++ .../SpToDoListUpdated.class.st | 5 + .../SpToDoListValidationError.class.st | 5 + .../SpToDoTask.class.st | 103 +++++++ .../SpToDoTaskDialog.class.st | 109 +++++++ .../SpToDoTaskPriority.class.st | 78 ++++++ src/Spec-Toplo-Examples-ToDoList/package.st | 1 + 8 files changed, 651 insertions(+) create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoList.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoListPresenter.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoListUpdated.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoListValidationError.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoTask.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoTaskDialog.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/SpToDoTaskPriority.class.st create mode 100644 src/Spec-Toplo-Examples-ToDoList/package.st diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoList.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoList.class.st new file mode 100644 index 0000000..02795ce --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoList.class.st @@ -0,0 +1,85 @@ +Class { + #name : #SpToDoList, + #superclass : #Object, + #instVars : [ + 'tasks', + 'announcer' + ], + #category : #'Spec-Toplo-Examples-ToDoList-Model' +} + +{ #category : #examples } +SpToDoList class >> exampleWithSomeTasks [ + + ^ self new + addNewTaskTitled: 'Create example' described: 'It should show most current features: +* Label +* List +* Button'; + addNewTaskTitled: 'Commit it' described: 'Place a good comment, of course.'; + addNewTaskTitled: 'Push to repository' described: 'This is obvious but...'; + in: [ :list | list tasks first priority: SpToDoTaskPriority high ]; + yourself +] + +{ #category : #accessing } +SpToDoList >> addNewTaskTitled: aString described: anotherString [ + + | newTask | + newTask := SpToDoTask titled: aString described: anotherString in: self. + newTask validate. + + tasks add: newTask. + self announceUpdated. + + ^ newTask +] + +{ #category : #announcements } +SpToDoList >> announceUpdated [ + + announcer announce: SpToDoListUpdated +] + +{ #category : #announcements } +SpToDoList >> announcer [ + + ^ announcer +] + +{ #category : #accessing } +SpToDoList >> delete: aToDoTask [ + + tasks remove: aToDoTask. + self announceUpdated. +] + +{ #category : #accessing } +SpToDoList >> deleteAll [ + + tasks removeAll. + self announceUpdated. +] + +{ #category : #initialization } +SpToDoList >> initialize [ + + super initialize. + tasks := OrderedCollection new. + announcer := Announcer new. +] + +{ #category : #accessing } +SpToDoList >> tasks [ + +^ tasks +] + +{ #category : #announcements } +SpToDoList >> whenUpdatedSend: aSelector to: aSubscriber [ + + announcer weak + when: SpToDoListUpdated + send: aSelector + to: aSubscriber +] diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoListPresenter.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoListPresenter.class.st new file mode 100644 index 0000000..e2db5cd --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoListPresenter.class.st @@ -0,0 +1,265 @@ +" +I'm a Spec2 presenter to manage a ToDo list. + +The goal is to be an example of use of Spec with Toplo as backend, but it should work with other backends, too. + +To open with Toplo, evaluate: +``` +| app model presenter | +app := SpApplication new + useBackend: #Toplo; + yourself. + +model := SpToDoList exampleWithSomeTasks. +presenter := SpToDoListPresenter newApplication: app model: model. +presenter open. +``` +" +Class { + #name : #SpToDoListPresenter, + #superclass : #SpPresenter, + #instVars : [ + 'listLabel', + 'list', + 'deleteButton', + 'toDoList', + 'toggleCompletedButton', + 'toolbar', + 'statusBar', + 'descriptionLabel', + 'descriptionTextArea' + ], + #category : #'Spec-Toplo-Examples-ToDoList-Presenters' +} + +{ #category : #layout } +SpToDoListPresenter class >> defaultLayout [ + + ^ SpBoxLayout newTopToBottom + borderWidth: 5; + spacing: 5; + add: (SpPanedLayout newLeftToRight + positionOfSlider: 60 percent; + add: (SpBoxLayout newTopToBottom + add: #listLabel expand: false; + add: #list; + add: (SpBoxLayout newLeftToRight + add: #toggleCompletedButton; + add: #deleteButton; + yourself) + expand: false; + yourself); + add: (SpBoxLayout newTopToBottom + add: #descriptionLabel expand: false; + add: #descriptionTextArea; + yourself); + yourself) +] + +{ #category : #actions } +SpToDoListPresenter >> completeAllTasks [ + + toDoList tasks do: [ :each | each completed: true ] +] + +{ #category : #initialization } +SpToDoListPresenter >> connectPresenters [ + + super connectPresenters. + + list whenSelectionChangedDo: [ :aSelection | + self refreshAccordingToSelectedTask ] +] + +{ #category : #initialization } +SpToDoListPresenter >> contextMenuForSelectedTask [ + + | aTask menu | + aTask := list selectedItem. + + menu := self newMenu. + + menu addGroup: [ :aGroup | + aGroup + addItem: [ :item | + item + name: 'Toggle done'; + iconName: #smallOk; + action: [ self toggleCompletedTask ] ]; + addItem: [ :item | + item + name: 'Delete'; + iconName: #remove; + action: [ self deleteTask ] ] ]. + + ^ menu +] + +{ #category : #actions } +SpToDoListPresenter >> deleteAllTasks [ + + toDoList deleteAll +] + +{ #category : #actions } +SpToDoListPresenter >> deleteTask [ + + toDoList delete: list selectedItem +] + +{ #category : #initialization } +SpToDoListPresenter >> initializePresenters [ + + listLabel := self newLabel + label: 'Tasks:'; + yourself. + + list := self newList + displayIcon: [ :aToDoTask | self taskIconFor: aToDoTask ]; + display: [ :aToDoTask | self taskDisplayFor: aToDoTask ]; + whenActivatedDo: [ self toggleCompletedTask ]; + contextMenu: [ self contextMenuForSelectedTask ]; + yourself. + + descriptionLabel := self newLabel + label: 'Description:'; + yourself. + + descriptionTextArea := self newText + disable; + yourself. + + toggleCompletedButton := self newButton + label: 'Toggle done'; + icon: (self iconNamed: #smallOk); + action: [ self toggleCompletedTask ]; + yourself. + + deleteButton := self newButton + label: 'Delete'; + icon: (self iconNamed: #remove); + action: [ self deleteTask ]; + yourself. + + self initializeToolbar. + statusBar := self newLabel. + + self refresh. +] + +{ #category : #initialization } +SpToDoListPresenter >> initializeToolbar [ + + toolbar := self newToolbar. + + toolbar addItem: (self newToolbarButton + icon: (self iconNamed: #smallAdd); + label: 'Create'; + action: [ self openAddTaskDialog ]; + yourself). + + toolbar addItemRight: (self newToolbarButton + icon: (self iconNamed: #smallOk); + label: 'All done'; + action: [ self completeAllTasks ]; + yourself). + + toolbar addItemRight: (self newToolbarButton + icon: (self iconNamed: #remove); + label: 'Clean'; + action: [ self deleteAllTasks ]; + yourself). +] + +{ #category : #initialization } +SpToDoListPresenter >> initializeWindow: aWindowPresenter [ + + aWindowPresenter + title: 'Demo @ ' , aWindowPresenter application backend asString; + initialExtent: 600 @ 300; + centered; + statusBar: statusBar; + toolbar: toolbar. +] + +{ #category : #actions } +SpToDoListPresenter >> openAddTaskDialog [ + + (SpToDoTaskDialog newApplication: self application model: toDoList) + parentWindow: self window; + openDialog +] + +{ #category : #private } +SpToDoListPresenter >> presentersThatRequireTaskSelected [ + + ^ { deleteButton. toggleCompletedButton } +] + +{ #category : #private } +SpToDoListPresenter >> refresh [ + + list unselectAll. + list items: toDoList tasks. + statusBar label: ('Number of tasks: {1}' format: { toDoList tasks size}). + self refreshAccordingToSelectedTask. +] + +{ #category : #private } +SpToDoListPresenter >> refreshAccordingToSelectedTask [ + + | hasSelection | + hasSelection := list selection isEmpty not. + + self refreshDescriptionTextArea. + + self presentersThatRequireTaskSelected do: [ :each | + each enabled: hasSelection ] +] + +{ #category : #private } +SpToDoListPresenter >> refreshDescriptionTextArea [ + + | newDescription | + newDescription := list selectedItem + ifNil: [ '' ] + ifNotNil: [ :selectedTask | + selectedTask description ]. + + descriptionTextArea text: newDescription +] + +{ #category : #initialization } +SpToDoListPresenter >> setModelBeforeInitialization: aSpToDoList [ + + toDoList := aSpToDoList. + toDoList whenUpdatedSend: #refresh to: self +] + +{ #category : #private } +SpToDoListPresenter >> taskDisplayFor: aToDoTask [ + + ^ '{1} ({2} priority)' format: { + aToDoTask title. + aToDoTask priority name } +] + +{ #category : #private } +SpToDoListPresenter >> taskIconFor: aToDoTask [ + + | aForm | + aForm := self iconNamed: #smallOk. + + aToDoTask isCompleted + ifFalse: [ aForm := aForm dimmed: 0.2 ]. + + ^ aForm +] + +{ #category : #actions } +SpToDoListPresenter >> toggleCompletedTask [ + + | selectedTask | + selectedTask := list selectedItem. + selectedTask completed: selectedTask isCompleted not +] diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoListUpdated.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoListUpdated.class.st new file mode 100644 index 0000000..30a8bd3 --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoListUpdated.class.st @@ -0,0 +1,5 @@ +Class { + #name : #SpToDoListUpdated, + #superclass : #Announcement, + #category : #'Spec-Toplo-Examples-ToDoList-Model-Announcements' +} diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoListValidationError.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoListValidationError.class.st new file mode 100644 index 0000000..e7310b6 --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoListValidationError.class.st @@ -0,0 +1,5 @@ +Class { + #name : #SpToDoListValidationError, + #superclass : #Error, + #category : #'Spec-Toplo-Examples-ToDoList-Model-Errors' +} diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoTask.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoTask.class.st new file mode 100644 index 0000000..d77e9c9 --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoTask.class.st @@ -0,0 +1,103 @@ +Class { + #name : #SpToDoTask, + #superclass : #Object, + #instVars : [ + 'title', + 'description', + 'completed', + 'toDoList', + 'priority' + ], + #category : #'Spec-Toplo-Examples-ToDoList-Model' +} + +{ #category : #'instance creation' } +SpToDoTask class >> titled: aString described: anotherString in: aToDoList [ + + ^ self basicNew + initializeTitle: aString described: anotherString in: aToDoList; + yourself +] + +{ #category : #accessing } +SpToDoTask >> completed: aBoolean [ + + completed := aBoolean. + toDoList announceUpdated. +] + +{ #category : #accessing } +SpToDoTask >> description [ + + ^ description +] + +{ #category : #accessing } +SpToDoTask >> description: aString [ + + description := aString. + toDoList announceUpdated +] + +{ #category : #initialization } +SpToDoTask >> initializeTitle: aString described: anotherString in: aSpToDoList [ + + self initialize. + toDoList := aSpToDoList. + title := aString. + description := anotherString. + completed := false. + priority := SpToDoTaskPriority medium. +] + +{ #category : #testing } +SpToDoTask >> isCompleted [ + + ^ completed +] + +{ #category : #printing } +SpToDoTask >> printOn: aStream [ + + "Append a sequence of characters to aStream that identify the receiver." + + super printOn: aStream. + aStream + nextPutAll: ' title: '; + print: title. + aStream + nextPutAll: ' completed: '; + print: completed +] + +{ #category : #accessing } +SpToDoTask >> priority [ + ^ priority +] + +{ #category : #accessing } +SpToDoTask >> priority: aPriority [ + + priority := aPriority. + toDoList announceUpdated +] + +{ #category : #accessing } +SpToDoTask >> title [ + + ^ title +] + +{ #category : #accessing } +SpToDoTask >> title: aString [ + + title := aString. + toDoList announceUpdated. +] + +{ #category : #validating } +SpToDoTask >> validate [ + + title isEmpty ifTrue: [ + SpToDoListValidationError signal: 'Title is empty' ] +] diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskDialog.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskDialog.class.st new file mode 100644 index 0000000..c3e56ca --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskDialog.class.st @@ -0,0 +1,109 @@ +Class { + #name : #SpToDoTaskDialog, + #superclass : #SpDialogPresenter, + #instVars : [ + 'task', + 'titleLabel', + 'titleInput', + 'descriptionLabel', + 'descriptionTextArea', + 'toDoList', + 'priorityLabel', + 'priorityDropList' + ], + #category : #'Spec-Toplo-Examples-ToDoList-Presenters' +} + +{ #category : #layout } +SpToDoTaskDialog class >> defaultLayout [ + + ^ SpBoxLayout newVertical + borderWidth: 5; + spacing: 5; + add: #titleLabel expand: false; + add: #titleInput expand: false; + add: (SpBoxLayout newHorizontal + add: #priorityLabel expand: false; + add: #priorityDropList; + yourself) + expand: false; + add: #descriptionLabel expand: false; + add: #descriptionTextArea; + yourself + +] + +{ #category : #actions } +SpToDoTaskDialog >> accept [ + + [ | newTask | + newTask := toDoList addNewTaskTitled: titleInput text described: descriptionTextArea text. + newTask priority: priorityDropList selectedItem. + titleInput text: ''. + super accept. + ] on: SpToDoListValidationError + do: [ :error | self showError: error ]. + +] + +{ #category : #initialization } +SpToDoTaskDialog >> initializeDialogWindow: aDialogWindowPresenter [ + + super initializeDialogWindow: aDialogWindowPresenter. + aDialogWindowPresenter + initialExtent: 300 @ 250; + title: 'Create a new task'; + addDefaultButton: 'Add to List' do: [ :presenter | self accept ]; + addButton: 'Cancel' do: [ :presenter | self cancel ] +] + +{ #category : #initialization } +SpToDoTaskDialog >> initializePresenter [ + + super initializePresenter. + + titleLabel := self newLabel + label: 'Title:'; + yourself. + + titleInput := self newTextInput. + + + priorityLabel := self newLabel + label: 'Priority:'; + yourself. + + priorityDropList := self newDropList + display: [ :item | + item + ifNil: [ '-- none ---' ] + ifNotNil: [ item name ] ]; + displayIcon: [ :item | self iconNamed: #empty ]; + items: SpToDoTaskPriority allPredefined; + selectIndex: 1; + yourself. + " display: [ :item | item name ];" + + + descriptionLabel := self newLabel + label: 'Description:'; + yourself. + + descriptionTextArea := self newText +] + +{ #category : #initialization } +SpToDoTaskDialog >> setModelBeforeInitialization: aSpToDoList [ + + toDoList := aSpToDoList +] + +{ #category : #actions } +SpToDoTaskDialog >> showError: error [ + + self application newInform + title: 'Validation Error'; + label: error messageText; + parentWindow: self window; + openDialog +] diff --git a/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskPriority.class.st b/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskPriority.class.st new file mode 100644 index 0000000..a5a5592 --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/SpToDoTaskPriority.class.st @@ -0,0 +1,78 @@ +Class { + #name : #SpToDoTaskPriority, + #superclass : #Object, + #instVars : [ + 'name', + 'rank' + ], + #classInstVars : [ + 'medium', + 'high', + 'low' + ], + #category : #'Spec-Toplo-Examples-ToDoList-Model' +} + +{ #category : #accessing } +SpToDoTaskPriority class >> allPredefined [ + + ^ { self low. self high. self medium } +] + +{ #category : #accessing } +SpToDoTaskPriority class >> high [ + + ^ high ifNil: [ high := self newNamed: 'High' rank: 90 percent ] +] + +{ #category : #accessing } +SpToDoTaskPriority class >> low [ + + ^ low ifNil: [ low := self newNamed: 'Low' rank: 10 percent ] +] + +{ #category : #accessing } +SpToDoTaskPriority class >> medium [ + + ^ medium ifNil: [ medium := self newNamed: 'Medium' rank: 50 percent ] +] + +{ #category : #'as yet unclassified' } +SpToDoTaskPriority class >> newNamed: aString rank: aNumber [ + + ^ self basicNew + initializeNamed: aString rank: aNumber; + yourself +] + +{ #category : #initialization } +SpToDoTaskPriority >> initializeNamed: aString rank: aNumber [ + + self initialize. + + name := aString. + rank := aNumber. +] + +{ #category : #accessing } +SpToDoTaskPriority >> name [ + + ^ name +] + +{ #category : #printing } +SpToDoTaskPriority >> printOn: aStream [ + + "Append a sequence of characters to aStream that identify the receiver." + + super printOn: aStream. + aStream + nextPutAll: ' name: '; + print: name +] + +{ #category : #accessing } +SpToDoTaskPriority >> rank [ + + ^ rank +] diff --git a/src/Spec-Toplo-Examples-ToDoList/package.st b/src/Spec-Toplo-Examples-ToDoList/package.st new file mode 100644 index 0000000..664d4df --- /dev/null +++ b/src/Spec-Toplo-Examples-ToDoList/package.st @@ -0,0 +1 @@ +Package { #name : #'Spec-Toplo-Examples-ToDoList' }