The idea for this program is simple: whatever text you have on the clipboard, click on a handy little tray icon and it will be saved to an SQLite database. Nothing seems to happen, but the text is saved in a new record.
There is also the means to search for notes already saved. This is done in a window that appears when you middle-click the tray icon. I would have preferred it to appear on right-clicking the icon, but right-clicking can only make a menu appear. That is what right-clicks do: they show contextual menus. Now, middle-clicking is fine if you have a mouse with a middle wheel or button that can be clicked. For those of us who use a laptop’s trackpad, middle-clicking is simulated by clicking both left and right buttons simultaneously. On KDE there is an option in System Settings that can disable this, but by default it is enabled.
Experiment with the main window being hidden when the application starts if you like, or not theskiptaskbar property so the minimised main window does not get listed with the other open applications in the panel, but the effects did not appeal at all so they have been left at their defaults.
A Google image search will find a suitable picture for the tray icon:
It does not need to be any particular size. It scales itself nicely when the program runs.
The window has a large text area to show a note, and a textbox at the top left in which to type search text. There are also three labels that display the date/time when the note was saved, the position of the note among all the notes that have been found with that search text in them, and, for interest, the record ID of the note.
The tray icon (bottom left corner) can be placed anywhere. It does not appear in the window. It appears in the system tray (usually at the bottom right corner of the screen, in KDE’s default panel).
The tray icon comes in its own component, so check Project > Properties to see that it and the database components are included:
In summary, to use this notebook:
Sometimes it is useful to save the text then bring up the window and add some key words that will help you find the note again. I sometimes add the words JOKE or QUOTE or FAMILY HISTORY. That way, by typing “QUOTE” in the search box, all my quotes appear and I can step through them one at a time. Copying all selected notes would be a useful feature to add.
The SQL select statement appears in the window caption, for interest.
For good measure, a TEXT menu has four entries for adjusting text:
Type Extra… positions the cursor at the end of the text of the visible note, ready for you to type something extra.
Tidy gets rid of multiple spaces, multiple tabs and multiple blank lines, and removes leading and trailing spaces.
Sentences joins broken sentences. Text copied from emails often has distinct lines.
Double-space separates the paragraphs with a blank line.
These text operations work on the whole of the text if there is no selection, or on the selected text if there is a selection. The shortcuts are there because often I find myself doing the last three in quick succession to get the text looking decent.
The other menus are:
The database is an SQLite file. You might wonder where the OPEN and NEW menu items are. For simplicity, the program creates a new database when it opens if none exists with the name and in the place it expects to find it. It is calledNotebook.sqlite and it is in the Home directory. It is easy enough to change the code to make it in the Documents directory if you wish.
File > Backup is a useful menuitem that copies the database file to a Backups folder in the Documents folder, date-stamping the file name to show when it was created and to not interfere with earlier backups.
File > Renumber makes the record ID’s sequential, with no gaps. It, and the Vacuum item below it, are unnecessary. Vacuum tidies the internal structure of the database file. It uses SQLite’s inbuilt vacuum command.
File > Quit closes the application.File > Hide makes the window invisible. Clicking the close box on the window also only hides the window; it does not close the application. For this trick, the window’sPersistent property is set totrue. The window persists, invisibly, when its close box is clicked.
When you type in the search textbox an SQL Select statement selects all the notes that contain that text. It is a simple search: it does not search for notes containing any of the words, ordered from best to least matching. It searches for exactly the text that is typed. As you type each letter the search is performed. A public property in themdb module,Publicrs As Result, stores the results of the search.Notes > Show All clears the search and finds all the notes. This happens when the program starts: all notes are selected.
Notes > New… lets you type a new note that is saved when you leave the text area.
Notes > Delete deletes the note currently displayed from the database.
Notes > Clear All clears all notes from the database. There is no undo but there is a chance to bail.
Window properties are:
Arrangement = Vertical
Stacking = Above
Width = 800
Height = 500
The Tray Icon (ti1) properties that need to be set are:
Tooltip = Click to save clipboard textVisible = True
Be sure to set it to visible or you will not see the tray icon when the program is run, and there will be no elegant way of quitting the program when it is running after you hide the window that shows initially.
TheStacking property ensures that the window remains above other windows. If you click in the window belonging to your web browser, for example, the Notebook window does not get covered by the browser window but remains on top, floating above it. This can be useful for utility type programs.
The last thing to note before getting to the code is the use of the keyboard’s arrow keys. UP and DOWN take you to the first and last of the found notes respectively. LEFT and RIGHT step you back or forwards through the found notes. “Found notes” means those that are found to have the string of text in them that you typed in the textbox, or, if nothing has been typed, all the notes.
A few comments follow the code.
Code relating to the database is collected in a module calledmdb.
' Gambas module filePublicdb1AsNewConnectionPublicrsAsResultPublicSQLSelAsStringPublicSubCreateDatabase()db1.Type="sqlite"db1.host=User.homedb1.name=""'delete an existing Notebook.sqliteIfExist(User.home&"/Notebook.sqlite")ThenKillUser.home&"/Notebook.sqlite"Endif'create Notebook.sqlitedb1.Opendb1.Databases.Add("Notebook.sqlite")db1.CloseEndPublicSubConnectDatabase()db1.Type="sqlite"db1.host=User.homedb1.name="Notebook.sqlite"db1.OpenSelectAllNotesDebug("Notes file connected:<br>"&db1.Host&/db1.Name&"<br><br>"&rs.Count&" records")EndPublicSubMakeTable()DimhTableAsTabledb1.name="Notebook.sqlite"db1.OpenhTable=db1.Tables.Add("Notes")hTable.Fields.Add("KeyID",db.Integer)hTable.Fields.Add("Created",db.Date)hTable.Fields.Add("Note",db.String)hTable.PrimaryKey=["KeyID"]hTable.UpdateMessage("Notes file created:<br>"&db1.Host&/db1.Name)EndPublicSubSelectAllNotes()rs=db1.Exec("SELECT * FROM Notes")FMain.Caption="SELECT * FROM Notes"rs.MoveLastEndPublicSubMassage(zAsString)AsStringWhileInStr(z,"''")>0'this avoids a build-up of single apostrophesReplace(z,"''","'")WendReturnReplace(z,"'","''")EndPublicSubAddRecord(sAsString,tAsDate)AsStringDimrs1AsResultDimNextIDAsIntegerIfrs.Max=-1ThenNextID=1ElseNextID=db1.Exec("SELECT Max(KeyID) AS TheMax FROM Notes")!TheMax+1db1.Beginrs1=db1.Create("Notes")rs1!KeyID=NextIDrs1!Created=t'timers1!Note=Massage(s)rs1.Updatedb1.CommitSelectAllNotesReturnNextIDCatchdb1.RollbackMessage.Error(Error.Text)EndPublicSubUpdateRecord(RecNumAsInteger,NewTextAsString)db1.Exec("UPDATE Notes SET Note='"&Massage(NewText)&"' WHERE KeyID="&RecNum)DimposAsInteger=rs.Index'Refresh the result cursor, so the text in it is updated as well as in the database file. This is tricky.IfIsNull(SQLSel)Thenrs=db.Exec("SELECT * FROM Notes")Elsers=db.Exec(SQLSel)'SQLSel is the last search, set by typing in tbSearchrs.MoveTo(pos)'Ooooh yes! It did it.CatchMessage.Error("<b>Update error.</b><br><br>"&Error.Text)EndPublicSubMoveRecord(KeyCodeAsInteger)AsBooleanIfrs.Count=0ThenReturnFalseSelectCaseKeyCodeCaseKey.LeftIfrs.Index>0Thenrs.MovePreviousElsers.MoveLastReturnTrueCaseKey.RightIfrs.Index<rs.MaxThenrs.MoveNextElsers.MoveFirstReturnTrueCaseKey.Uprs.MoveFirstReturnTrueCaseKey.Downrs.MoveLastReturnTrueEndSelectReturnFalseEndPublicSubClearAll()IfMessage.Warning("Delete all notes? This cannot be undone.","Ok","Cancel")=1Thendb1.Exec("DELETE FROM Notes")SelectAllNotesEndifEndPublicSubDeleteRecord(RecNumAsInteger)db1.Exec("DELETE FROM Notes WHERE KeyID='"&RecNum&"'")SelectAllNotesEndPublicSubSearchFor(sAsString)SQLSel="SELECT * FROM Notes WHERE Note LIKE '%"&Massage(s)&"%'"IfIsNull(s)ThenSelectAllNotesSQLSel=""ElseFMain.Caption=SQLSelrs=db1.Exec(SQLSel)EndifEndPublicSubRenumber()DimresAsResult=db.Exec("SELECT * FROM Notes ORDER BY KeyID")DimiAsInteger=1DimxAsIntegerApplication.Busy+=1Whileres.Availablex=res!KeyIDdb.Exec("UPDATE Notes SET KeyID="&i&" WHERE KeyID="&x)i+=1res.MoveNextWendSelectAllNotesApplication.Busy-=1EndPublicSubVacuum()AsStringDimfSize1,fSize2AsFloatfSize1=Stat(db1.Host&/db1.Name).Size/1000'kBdb1.Exec("Vacuum")fSize2=Stat(db1.Host&/db1.Name).Size/1000'kBDimUnitsAsString="kB"IffSize1>1000Then'megabyte rangefSize1/=1000fSize2/=1000Units="MB"EndifReturnFormat(fSize1,"#.0")&Units&" -> "&Format(fSize2,"#.0")&Units&" ("&Format(fSize1-fSize2,"#.00")&Units&")"EndPublicSubBackup()AsStringIfNotExist(User.Home&/"Documents/Backups/")ThenMkdirUser.Home&/"Documents/Backups"DimfnAsString="Notebook "&Format(Now,"yyyy-mm-dd hh-nn")DimsourceAsString=db1.Host&/db1.NameDimdestAsString=User.Home&/"Documents/Backups/"&fnTryCopysourceTodestIfErrorThenReturn"Couldn't save -> "&Error.TextElseReturn"Saved -> /Documents/Backups/"&fnEnd
' Gambas class filePublicOriginalTextAsStringPublicSubti1_Click()DimTimeAddedAsStringIfClipboard.Type=Clipboard.TextThenTimeAdded=mdb.AddRecord(Clipboard.Paste("text/plain"),Now())EndPublicSubForm_Open()IfNotExist(User.Home&/"Notebook.sqlite")Then'create notebook data filemdb.CreateDatabasemdb.MakeTablemdb.SelectAllNotesElsemdb.ConnectDatabaseShowRecordEndifEndPublicSubMenuQuit_Click()mdb.db1.Closeti1.DeleteQuitEndPublicSubForm_KeyPress()Ifmdb.MoveRecord(Key.Code)ThenShowRecordStopEventEndifEndPublicSubShowRecord()Ifmdb.rs.count=0ThenClearFieldsReturnEndifta1.Text=Replace(mdb.rs!Note,"''","'")DimdAsDate=mdb.rs!CreatedlabTime.text=Format(d,gb.MediumDate)&" "&Format(d,gb.LongTime)labRecID.text=mdb.rs!KeyIDlabLocation.text=Str(mdb.rs.Index+1)&"/"&mdb.rs.CountOriginalText=ta1.TextEndPublicSubMenuClear_Click()mdb.ClearAllClearFieldslabTime.Text="No records"EndPublicSubMenuCopy_Click()Clipboard.Copy(ta1.Text)EndPublicSubMenuDeleteNote_Click()DimRecNumAsInteger=Val(labRecID.Text)mdb.DeleteRecord(RecNum)'after which all records selected; now to relocate...DimresAsResult=db.Exec("SELECT * FROM Notes WHERE KeyID<"&RecNum)res.MoveLastDimiAsInteger=res.Indexmdb.rs.MoveTo(i)ShowRecordEndPublicSubClearFields()ta1.Text=""labRecID.Text=""labTime.Text=""labLocation.Text=""EndPublicSubMenuNewNote_Click()ClearFieldsta1.SetFocusEndPublicSubta1_GotFocus()OriginalText=ta1.TextEndPublicSubta1_LostFocus()Ifta1.Text=OriginalTextThenReturn'no changeSaveOrUpdateEndPublicSubtbSearch_Change()mdb.SearchFor(tbSearch.Text)ShowRecordCatchMessage.Error(Error.Text)EndPublicSubta1_KeyPress()IfKey.Code=Key.EscThenMe.SetFocus'clear focus from textarea; this triggers a record updateEndPublicSubtbSearch_KeyPress()IfKey.Code=Key.EscThenMe.SetFocusEndPublicSubMenuShowAll_Click()mdb.SelectAllNotesShowRecordEndPublicSubKeepReplacing(InThisAsString,LookForAsString,BecomesAsString)AsStringDimzAsString=InThisWhileInStr(z,LookFor)>0z=Replace(z,LookFor,Becomes)WendReturnzEndPublicSubSaveOrUpdate()IfIsNull(labRecID.Text)Then'new recordIfIsNull(ta1.Text)ThenReturnDimdAsDate=Now()labRecID.Text=mdb.AddRecord(ta1.Text,d)labTime.text=Format(d,gb.MediumDate)&" "&Format(d,gb.LongTime)Else'updateIfIsNull(ta1.Text)Thenmdb.DeleteRecord(Val(labRecID.Text))ClearFields'maybe leave everything empty?Elsemdb.UpdateRecord(Val(labRecID.Text),ta1.Text)EndifEndifEndPublicSubMenuTidy_Click()OriginalText=ta1.TextIfIsNull(ta1.Text)ThenReturnDimzAsString=If(ta1.Selection.Length=0,Trim(ta1.Text),Trim(ta1.Selection.Text))z=KeepReplacing(z,gb.NewLine,"|")z=KeepReplacing(z,gb.Tab&gb.Tab,gb.Tab)z=KeepReplacing(z," "," ")z=KeepReplacing(z,"| ","|")z=KeepReplacing(z,"|"&gb.tab,"|")z=KeepReplacing(z,"||","|")z=KeepReplacing(z,"|",gb.NewLine)Ifta1.Selection.Length=0Thenta1.Text=zElseta1.Selection.Text=zSaveOrUpdateEndPublicSubMenuSentences_Click()OriginalText=ta1.TextIfIsNull(ta1.Text)ThenReturnDimzAsString=If(ta1.Selection.Length=0,Trim(ta1.Text),Trim(ta1.Selection.Text))z=KeepReplacing(z,gb.NewLine,"~")z=KeepReplacing(z,"~ ","~")z=KeepReplacing(z,".~","|")z=KeepReplacing(z,"~"," ")z=KeepReplacing(z," "," ")z=KeepReplacing(z,"|","."&gb.NewLine)Ifta1.Selection.Length=0Thenta1.Text=zElseta1.Selection.Text=zSaveOrUpdateEndPublicSubMenuUndo_Click()DimzAsString=ta1.Textta1.Text=OriginalTextOriginalText=zSaveOrUpdateEndPublicSubMenuRenumber_Click()mdb.RenumberShowRecordEndPublicSubMenuVacuum_Click()Me.Caption="File size -> "&mdb.Vacuum()EndPublicSubMenuBackup_Click()Me.Caption=mdb.Backup()EndPublicSubMenuTypeExtra_Click()ta1.SetFocusta1.Text&=gb.NewLine&gb.NewLineta1.Select(ta1.Text.Len)EndPublicSubMenuDoubleSpace_Click()OriginalText=ta1.TextIfIsNull(ta1.Text)ThenReturnDimzAsString=If(ta1.Selection.Length=0,Trim(ta1.Text),Trim(ta1.Selection.Text))z=KeepReplacing(z,gb.NewLine,"|")z=KeepReplacing(z,"||","|")z=KeepReplacing(z,"|",gb.NewLine&gb.NewLine)Ifta1.Selection.Length=0Thenta1.Text=zElseta1.Selection.Text=zSaveOrUpdateEndPublicSubti1_MiddleClick()Me.ShowMe.ActivatetbSearch.Text=""mdb.SelectAllNotesShowRecordEndPublicSubMenuHide_Click()Me.HideEnd
TheMassage(string) function is necessary for handling the saving of text that has single apostrophes in it. SQL statements use single apostrophes to surround strings. A single apostrophe in the string will terminate the string and what follows will be a syntax error as it will be incomprehensible. To include an apostrophe it has to be doubled. For example, to save the stringFred’s house it has to first be converted (“massaged”) toFred’’s house.
TheKeepReplacing(InThis, LookFor, ReplaceWithThis) function performs replacements until theLookFor string is no longer present. For example, if you wanted to remove multiple x’s fromabcxxxxdef and just have one singlex you cannot just useReplace(“abcxxxxdef”, “xx”, “x”), for this would produceabcxxdef. The first double-x becomes a singlex, and the second double-x becomes a singlex. You still have a double-x. You have to keep replacing until there are no more double-x’s.
That’s all, folks, except for the reference appendices. May I finish where I began, with a word of thanks to Benoît Minisini. This programming environment is a delight to use. With the gratitude of all of us users we sing, glass in hand, “For he’s a jolly good fellow, and so say all of us”.
Gerard Buzolic
25 September 2019
| Programming Gambas from Zip | ||
| ← Printing | Tray Icon Notebook | Afterword → |