diff options
38 files changed, 2515 insertions, 573 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f6667de3..2edd6a61 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,33 @@ Release notes for fpGUI ======================= +v1.0 (2013-04-09) + - Docview improvements: Bookmark maintenance, keyboard shortcuts, + image support in INF files, cross link support. + - Many improvements to the experimental Maximus IDE. + - More widget themeing improvements. + - Keyboard shortcut handling improvements. + - AggPas 2D graphics library merged into fpGUI as a new + cross-platform graphics backend. This is the first step in + adding AggPas support, and this is still work in progress, but + most functionality works already. Also added some 64-bit fixes + to the AggPas code. + - Many PDF report engine improvements. Also enhanced the look and + functionality of the Print Preview dialog. + - Lots more documentation added. This is an ongoing task. + - Big improvements to the TextEdit widget, that is used by Maximus + and the Nanoedit example project. + - Many improvements to the RichView component and it's supported + syntax - used by Docview. This widget is now also useable + in other projects, not just Docview. + - Improved fpGUI language translations. + - The UI Designer (forms designer) has also seen many improvements, + like new widgets added to the palette, more published properties + added to the Object Inspector window etc. + - Many, many more bug fixes and improvements. For a full list run: + git log v1.0...v0.8 + + v0.8 (2011-12-02) - New widget demos and many improvements to existing demos. - New application examples diff --git a/docs/branching_model/branching_model.inf b/docs/branching_model/branching_model.inf Binary files differnew file mode 100644 index 00000000..6f03331c --- /dev/null +++ b/docs/branching_model/branching_model.inf diff --git a/docs/branching_model/branching_model.ipf b/docs/branching_model/branching_model.ipf new file mode 100644 index 00000000..da500297 --- /dev/null +++ b/docs/branching_model/branching_model.ipf @@ -0,0 +1,644 @@ +.* Created by Graeme Geldenhuys <graemeg@gmail.com> - 2013-04-08 +.* This document is encoded as IBM code page 850. +.* +:userdoc. +:docprof toc=123. +:title.fpGUI Branching Model + +:h1.fpGUI branching model +:p.In this post I present the development model that I've introduced for all of +my projects (both at work and private) about a year ago, and which has turned +out to be very successful. I've been meaning to write about it for a while now, +but I've never really found the time to do so thoroughly, until now. I won't +talk about any of the projects' details, merely about the branching strategy +and release management. + +:cgraphic. + :hp2.Time increases to the right:ehp2. + + 0.1 0.2 1.0 1.5 + master ĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶ> + \Ä, / / / + | \ / / / + hotfixes ÄÄÄÄ|ÄÄÄoÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ/ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ/ÄÄÄÄÄÄÄÄÄÄÄ> + | \ / / + | \ / / + release ÄÄÄÄ|ÄÄÄÄÄÄ\ÄÄÄÄÄÄÄÄÄÄÄÄÄoĶĶĶoĶĶĶoĶĶĶoÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄoÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> + branches | \ / \ \ / \ + \ \ / \ \ / \ + develop ĶĶĶĶĶĶoĶĶĶoĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶoĶĶĶoĶĶĶĶĶoĶĶĶoĶĶĶĶĶĶĶ> + \Ä, / \ / / + | \ / \ / / + feature1 ÄÄÄÄÄÄÄ|ÄÄÄoĶĶĶoĶĶĶoÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄoĶĶĶoĶĶĶoÄÄÄ/ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> + | / + \ / + feature2 ÄÄÄÄÄÄÄÄÄoĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶoÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> + + + + :hp2.Key::ehp2. + ĶĶĶ and diagonal lines represent active development + ÄÄÄ represents no development, just time that has passed + o is a commit in the repository + 0.1 Numbers above the master branch is release tags +:ecgraphic. + +:p. +Here is an :link reftype=hd res=123.alternative overview graph:elink., which +hides the "time has passed" lines. + +:p. +It focuses around Git [http://git-scm.com/] as the tool for the versioning of all of our source code. + +.* ------------------------------------- +:h2 res=123 hide.Alternative overview graph +:cgraphic. + :hp2.Time increases to the right:ehp2. + + 0.1 0.2 1.0 1.5 + master ĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶ> + \Ä, / / / + | \ / / / + hotfixes | o / / + | \ / / + | \ / / + release | \ oĶĶĶoĶĶĶoĶĶĶo o + branches | \ / \ \ / \ + \ \ / \ \ / \ + develop ĶĶĶĶĶĶoĶĶĶoĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶoĶĶĶĶĶoĶĶĶoĶĶĶĶĶoĶĶĶoĶĶĶĶĶĶĶ> + \Ä, / \ / / + | \ / \ / / + feature1 | oĶĶĶoĶĶĶo oĶĶĶoĶĶĶo / + | / + \ / + feature2 oĶĶĶoĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶoĶĶĶĶĶĶĶĶĶĶo + + + + :hp2.Key::ehp2. + ĶĶĶ and diagonal lines represent active development + o is a commit in the repository + 0.1 Numbers above the master branch is release tags +:ecgraphic. + +.* ------------------------------------- +:h2.Why Git? +:p. +For a thorough discussion on the pros and cons of Git compared to centralized +source code control systems, see [http://whygitisbetterthanx.com/] the web [http://git.or.cz/gitwiki/GitSvnComparsion]. There are plenty of flame wars going +on there. As a developer, I prefer Git above all other tools around today. Git +really changed the way developers think of merging and branching. From the +classic CVS/Subversion world I came from, merging/branching has always been +considered a bit scary ("beware of merge conflicts, they bite you!") and +something you only do every once in a while. + +:p. +But with Git, these actions are extremely cheap and simple, and they are +considered one of the core parts of your daily workflow, really. For example, +in CVS/Subversion books, branching and merging is first discussed in the later +chapters (for advanced users), while in every Git book, it's already covered +in chapter 3 (basics). + +:p. +As a consequence of its simplicity and repetitive nature, branching and merging +are no longer something to be afraid of. Version control tools are supposed to +assist in branching/merging more than anything else. + +:p. +Enough about the tools, let's head onto the development model. The model that +I'm going to present here is essentially no more than a set of procedures that +every team member has to follow in order to come to a managed software +development process. + +.* ------------------------------------- +:h2.Decentralised but centralised +:p. +The repository setup that we use and that works well with this branching model, +is that with a central "truth" repo. Note that this repo is only considered to +be the central one (since Git is a DVCS, there is no such thing as a central +repo at a technical level). We will refer to this repo as origin, since this +name is familiar to all Git users. + +.* :artwork align=center name='centr-decentr.bmp'. +:cgraphic. + ŚÄÄÄÄÄÄÄÄÄÄæ ŚÄÄÄÄÄÄÄÄÄÄæ + ³ ³ ³ ³ + ³ alice ³<ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>³ david ³ + ³ ³ ³ ³ + ³ ³<ÄÄÄÄæ ŚÄÄÄÄÄÄ>³ ³ + ĄÄÄÄÄÄÄÄÄÄÄŁ ³ ³ ĄÄÄÄÄÄÄÄÄÄÄŁ + ^ ³ ŚÄÄÄÄÄÄÄÄÄÄæ ³ ^ + ³ ĄÄÄÄÄ>³ ³<ÄÄÄÄÄŁ ³ + ³ ³ origin ³ ³ + ³ ³ ³ ³ + ³ ŚÄÄÄÄ>³ ³<ÄÄÄÄÄæ ³ + v ³ ĄÄÄÄÄÄÄÄÄÄÄŁ ³ v + ŚÄÄÄÄÄÄÄÄÄÄæ ³ ³ ŚÄÄÄÄÄÄÄÄÄÄæ + ³ ³<ÄÄÄÄŁ ĄÄÄÄÄÄÄ>³ ³ + ³ bob ³ ³ clair ³ + ³ ³<ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>³ ³ + ³ ³ ³ ³ + ĄÄÄÄÄÄÄÄÄÄÄŁ ĄÄÄÄÄÄÄÄÄÄÄŁ +:ecgraphic. + +:p. +Each developer pulls and pushes to origin. But besides the centralized push-pull + relationships, each developer may also pull changes from other peers to form +sub teams. For example, this might be useful to work together with two or more +developers on a big new feature, before pushing the work in progress to origin +prematurely. In the figure above, there are subteams of Alice and Bob, Alice +and David, and Clair and David. + +:p. +Technically, this means nothing more than that Alice has defined a Git remote, +named bob, pointing to Bob's repository, and vice versa. + + +.* ------------------------------------- +:h2.The main branches +:p. +At the core, the development model is greatly inspired by existing models out +there. The central repo holds two main branches with an infinite lifetime: + +:ul compact. +:li.master +:li.develop +:eul. + +:cgraphic. + (1) (2) (3) + master ÄÄÄAÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄFÄÄÄÄÄÄÄIÄÄÄÄÄÄÄÄÄÄÄÄ> + \ / / + develop ÄÄÄÄÄBÄÄÄCÄÄÄDÄÄÄEÄÄÄGÄÄÄHÄÄÄJÄÄÄKÄÄÄÄÄÄ> + (4) + + + 1) Initial production version + 2) Next production version + 3) Next production version + 4) Work in progress on "next release" +:ecgraphic. + +.* :cgraphic. +.* develop master +.* | | +.* | o Initial production version +.* | /| +.* | ,-------------' | +.* |/ | +.* o | +.* | | +.* o | +.* | | +.* o | +.* | | +.* o | +.* |\ | +.* | `-------------, | +.* o \| +.* | o Next production version +.* o | +.* |\ | +.* | `-------------, | +.* o \| +.* | o Next production version +.* Work in o | +.* progress on | | +.* "next release" | | +.* | | +.* v v +.* :ecgraphic. + +:p. +The :hp8.master:ehp8. branch at :hp8.origin:ehp8. should be familiar to every Git user. Parallel to +the :hp8.master:ehp8. branch, another branch exists called :hp8.develop:ehp8.. + +:p. +We consider :hp8.origin/master:ehp8. to be the main branch where the source code of HEAD +always reflects a production-ready state. + +:p. +We consider :hp8.origin/develop:ehp8. to be the main branch where the source code of HEAD +always reflects a state with the latest delivered development changes for the +next release. Some would call this the "integration branch". This is where any +automatic nightly builds are built from. + +:p. +When the source code in the :hp8.develop:ehp8. branch reaches a stable point and is ready +to be released, all of the changes should be merged back into :hp8.master:ehp8. somehow +and then tagged with a release number. How this is done in detail will be +discussed further on. + +:p. +Therefore, each time when changes are merged back into :hp8.master:ehp8., this is a new +production release by :hp1.definition:ehp1.. We tend to be very strict at this, so that +theoretically, we could use a Git hook script to automatically build and +roll-out our software to our production servers everytime there was a commit +on :hp8.master:ehp8.. + + +.* ------------------------------------- +:h2.Supporting branches +:p. +Next to the main branches master and develop, our development model uses a +variety of supporting branches to aid parallel development between team members, +ease tracking of features, prepare for production releases and to assist in +quickly fixing live production problems. Unlike the main branches, these +branches always have a limited life time, since they will be removed eventually. + +:p. +The different types of branches we may use are: + +:ul compact. +:li.Feature branches +:li.Release branches +:li.Hotfix branches +:eul. + +:p. +Each of these branches have a specific purpose and are bound to strict rules +as to which branches may be their originating branch and which branches must +be their merge targets. We will walk through them in a minute. + +:p. +By no means are these branches "special" from a technical perspective. The +branch types are categorized by how we use them. They are of course plain old +Git branches. + +.* ------------------------------------- +:h2.Feature branches +.* :artwork align=center name='feature-branch.bmp'. +:cgraphic. + develop ÄÄÄAÄÄÄBÄÄÄGÄÄÄHÄÄÄÄÄÄÄIÄÄÄJÄÄÄ> + \ / + feature ÄÄÄÄÄÄÄÄÄCÄÄÄDÄÄÄEÄÄÄFÄÄÄÄÄÄÄÄÄ> + branches +:ecgraphic. + + +.* :cgraphic. +.* feature +.* branches develop +.* | | +.* | o +.* | | +.* | o +.* | /| +.* | ,-------------' | +.* |/ o +.* o | +.* | | +.* o o +.* | | +.* o | +.* |\ | +.* | `-------------, | +.* | \| +.* | o +.* | | +.* | | +.* v v +.* :ecgraphic. + +:p. +May branch off from: :hp8.develop:ehp8. +.br +Must merge back into: :hp8.develop:ehp8. +.br +Branch naming convention: anything except :hp8.master:ehp8., :hp8.develop:ehp8., +:hp8.release-*:ehp8., or :hp8.hotfix-*:ehp8. + +:p. +Feature branches (or sometimes called topic branches) are used to develop new +features for the upcoming or a distant future release. When starting development +of a feature, the target release in which this feature will be incorporated may +well be unknown at that point. The essence of a feature branch is that it +exists as long as the feature is in development, but will eventually be merged +back into develop (to definitely add the new feature to the upcoming release) +or discarded (in case of a disappointing experiment). + +:p. +Feature branches typically exist in developer repos only, not in :hp8.origin:ehp8.. + +:h3.Creating a feature branch +:p. +When starting work on a new feature, branch off from the develop branch. + +:lm margin=5. +:xmp. +$ git checkout -b myfeature develop +Switched to a new branch "myfeature" +:exmp. +:lm margin=1. + +:h3.Incorporating a finished feature on develop +:p. +Finished features may be merged into the develop branch definitely add them to +the upcoming release: + +:lm margin=5. +:xmp. +$ git checkout develop +Switched to branch 'develop' +$ git merge --no-ff myfeature +Updating ea1b82a..05e9557 +(Summary of changes) +$ git branch -d myfeature +Deleted branch myfeature (was 05e9557). +$ git push origin develop +:exmp. +:lm margin=1. + +:p. +The :hp2.--no-ff:ehp2. flag causes the merge to always create a new commit object, even +if the merge could be performed with a fast-forward. This avoids losing +information about the historical existence of a feature branch and groups +together all commits that together added the feature. Compare: + +:cgraphic. + + :hp2.git merge --no--ff:ehp2. + + develop ÄÄÄAÄÄÄBÄÄÄÄÄÄÄÄÄÄÄFÄÄÄÄÄÄÄ> + \ / + feature ÄÄÄÄÄÄÄÄÄCÄÄÄDÄÄÄEÄÄÄÄÄÄÄÄÄ> + branches + + + :hp2.git merge:ehp2. + (plain) + + develop ÄÄÄAÄÄÄBÄÄÄCÄÄÄDÄÄÄEÄÄÄFÄÄÄ> + + + where C, D and E are the feature commits +:ecgraphic. + +:p. +In the latter case, it is impossible to see from the Git history which of the +commit objects together have implemented a feature -- you would have to manually +read all the log messages. Reverting a whole feature (i.e. a group of commits), +is a true headache in the latter situation, whereas it is easily done if the +:hp2.--no-ff:ehp2. flag was used. + +:p. +Yes, it will create a few more (empty) commit objects, but the gain is much +bigger that that cost. + +:p. +Unfortunately, I have not found a way to make :hp2.--no-ff:ehp2. the default behaviour of +:hp2.git merge:ehp2. yet, but it really should be. + + +.* ------------------------------------- +:h2.Release branches +:p. +May branch off from: :hp8.develop:ehp8. +.br +Must merge back into: :hp8.develop:ehp8. and :hp8.master:ehp8. +.br +Branch naming convention: :hp8.release-*:ehp8. + +:p. +Release branches support preparation of a new production release. They allow +for last-minute dotting of i's and crossing t's. Furthermore, they allow for +minor bug fixes and preparing meta-data for a release (version number, build +dates, etc.). By doing all of this work on a release branch, the :hp8.develop:ehp8. branch +is cleared to receive features for the next big release. + +:p. +The key moment to branch off a new release branch from :hp8.develop:ehp8. is when develop +(almost) reflects the desired state of the new release. At least all features +that are targeted for the release-to-be-built must be merged in to develop at +this point in time. All features targeted at future releases may not -- they must +wait until after the release branch is branched off. + +:p. +It is exactly at the start of a release branch that the upcoming release gets +assigned a version number -- not any earlier. Up until that moment, the :hp8.develop:ehp8. +branch reflected changes for the "next release", but it is unclear whether +that "next release" will eventually become 0.3 or 1.0, until the release branch +is started. That decision is made on the start of the release branch and is +carried out by the project's rules on version number bumping. + +:h3.Creating a release branch +:p. +Release branches are created from the develop branch. For example, say version +1.1.5 is the current production release and we have a big release coming up. +The state of develop is ready for the "next release" and we have decided that +this will become version 1.2 (rather than 1.1.6 or 2.0). So we branch off and +give the release branch a name reflecting the new version number: + +:lm margin=5. +:xmp. +$ git checkout -b release-1.2 develop +Switched to a new branch "release-1.2" +$ ./bump-version.sh 1.2 +Files modified successfully, version bumped to 1.2. +$ git commit -a -m "Bumped version number to 1.2" +[release-1.2 74d9424] Bumped version number to 1.2 +1 files changed, 1 insertions(+), 1 deletions(-) +:exmp. +:lm margin=1. + +:p. +After creating a new branch and switching to it, we bump the version number. +Here, :hp2.bump-version.sh:ehp2. is a fictional shell script that changes some files in +the working copy to reflect the new version. (This can of course be a manual +change -- the point being that some files change.) Then, the bumped version number +is committed. + +:p. +This new branch may exist there for a while, until the release may be rolled +out definitely. During that time, bug fixes may be applied in this branch +(rather than on the :hp8.develop:ehp8. branch). Adding large new features here is strictly +prohibited. They must be merged into :hp8.develop:ehp8., and therefore, wait for the next +big release. + +:h3.Finishing a release branch +:p. +When the state of the release branch is ready to become a real release, some +actions need to be carried out. First, the release branch is merged into :hp8.master:ehp8. +(since every commit on :hp8.master:ehp8. is a new release by definition, remember). Next, +that commit on :hp8.master:ehp8. must be tagged for easy future reference to this +historical version. Finally, the changes made on the release branch need to be +merged back into :hp8.develop:ehp8., so that future releases also contain these bug fixes. + +:p. +The first two steps in Git: + +:lm margin=5. +:xmp. +$ git checkout master +Switched to branch 'master' +$ git merge --no-ff release-1.2 +Merge made by recursive. +(Summary of changes) +$ git tag -a 1.2 +:exmp. +:lm margin=1. + +:p. +The release is now done, and tagged for future reference. You might as well +use the -s or -u <key> flags to sign your tag cryptographically. + +:p. +To keep the changes made in the release branch, we need to merge those back +into :hp8.develop:ehp8., though. In Git: + +:lm margin=5. +:xmp. +$ git checkout develop +Switched to branch 'develop' +$ git merge --no-ff release-1.2 +Merge made by recursive. +(Summary of changes) +:exmp. +:lm margin=1. + +:p. +This step may well lead to a merge conflict (probably even, since we have +changed the version number). If so, fix it and commit. + +:p. +Now we are really done and the release branch may be removed, since we don't +need it anymore: + +:lm margin=5. +:xmp. +$ git branch -d release-1.2 +Deleted branch release-1.2 (was ff452fe). +:exmp. +:lm margin=1. + + + +.* ------------------------------------- +:h2.Hotfix branches +:p. +May branch off from: :hp8.master:ehp8. +.br +Must merge back into: :hp8.develop:ehp8. and :hp8.master:ehp8. +.br +Branch naming convention: :hp8.hotfix-*:ehp8. + +:p. +Hotfix branches are very much like release branches in that they are also meant +to prepare for a new production release, albeit unplanned. They arise from the +necessity to act immediately upon an undesired state of a live production version. +When a critical bug in a production version must be resolved immediately, a +hotfix branch may be branched off from the corresponding tag on the master +branch that marks the production version. + +:p. +The essence is that work of team members (on the :hp8.develop:ehp8. branch) can continue, +while another person is preparing a quick production fix. + + +:h3.Creating the hotfix branch +:p. +Hotfix branches are created from the :hp8.master:ehp8. branch. For example, say version +1.2 is the current production release running live and causing troubles due +to a severe bug. But changes on :hp8.develop:ehp8. are yet unstable. We may then branch +off a hotfix branch and start fixing the problem: + +:lm margin=5. +:xmp. +$ git checkout -b hotfix-1.2.1 master +Switched to a new branch "hotfix-1.2.1" +$ ./bump-version.sh 1.2.1 +Files modified successfully, version bumped to 1.2.1. +$ git commit -a -m "Bumped version number to 1.2.1" +[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1 +1 files changed, 1 insertions(+), 1 deletions(-) +:exmp. +:lm margin=1. + +:p. +Don't forget to bump the version number after branching off! + +:p. +Then, fix the bug and commit the fix in one or more separate commits. + +:lm margin=5. +:xmp. +$ git commit -m "Fixed severe production problem" +[hotfix-1.2.1 abbe5d6] Fixed severe production problem +5 files changed, 32 insertions(+), 17 deletions(-) +:exmp. +:lm margin=1. + + +:h3.Finishing a hotfix branch +:p. +When finished, the bugfix needs to be merged back into :hp8.master:ehp8., but also needs +to be merged back into :hp8.develop:ehp8., in order to safeguard that the bugfix is +included in the next release as well. This is completely similar to how +release branches are finished. + +:p. +First, update :hp8.master:ehp8. and tag the release. + +:lm margin=5. +:xmp. +$ git checkout master +Switched to branch 'master' +$ git merge --no-ff hotfix-1.2.1 +Merge made by recursive. +(Summary of changes) +$ git tag -a 1.2.1 +:exmp. +:lm margin=1. + +:p. +You might as well use the -s or -u <key> flags to sign your tag cryptographically. + +:p. +Next, include the bugfix in :hp8.develop:ehp8., too: + +:lm margin=5. +:xmp. +$ git checkout develop +Switched to branch 'develop' +$ git merge --no-ff hotfix-1.2.1 +Merge made by recursive. +(Summary of changes) +:exmp. +:lm margin=1. + +:p. +The one exception to the rule here is that, :hp2.when a release branch currently +exists, the hotfix changes need to be merged into that release branch, instead +of develop:ehp2.. Back-merging the bugfix into the release branch will eventually +result in the bugfix being merged into :hp8.develop:ehp8. too, when the release branch is +finished. (If work in :hp8.develop:ehp8. immediately requires this bugfix and cannot wait +for the release branch to be finished, you may safely merge the bugfix into +:hp8.develop:ehp8. now already as well.) + +:p. +Finally, remove the temporary branch: + +:lm margin=5. +:xmp. +$ git branch -d hotfix-1.2.1 +Deleted branch hotfix-1.2.1 (was abbe5d6). +:exmp. +:lm margin=1. + + +:h2.Summary +:p. +While there is nothing really shocking new to this branching model, the "big +picture" figure that this post began with has turned out to be tremendously +useful in our projects. It forms an elegant mental model that is easy to +comprehend and allows team members to develop a shared understanding of the +branching and releasing processes. + +:p. +This document is based on an article found at http://nvie.com/posts/a-successful-git-branching-model/ + + + + +:euserdoc. + diff --git a/docview/TODO.txt b/docview/TODO.txt index 886161c8..d0cccc7c 100644 --- a/docview/TODO.txt +++ b/docview/TODO.txt @@ -23,11 +23,11 @@ Todo list [ ] - RichTextView component issue. Incompatible with original OS/2 VIEW. When resetting a font style (eg: italics to normal), it should not affect a previously set font color, unless a new font color is explicitly set. -[ ] - External links are not supported yet. [o] - Implement Image support in INF or HLP files. +[x] - External links are not supported yet. [x] - Access Violation when the application terminates. [x] - Settings / Customization dialog is needed. [x] - Create help file for DocView listing all supported features etc.. diff --git a/docview/docs/codepage_2_unicode_mapping/reame.txt b/docview/docs/codepage_2_unicode_mapping/readme.txt index 33b26a53..33b26a53 100644 --- a/docview/docs/codepage_2_unicode_mapping/reame.txt +++ b/docview/docs/codepage_2_unicode_mapping/readme.txt diff --git a/docview/docs/docview.ipf b/docview/docs/docview.ipf index 567ef1bd..94099582 100644 --- a/docview/docs/docview.ipf +++ b/docview/docs/docview.ipf @@ -422,6 +422,7 @@ search result. Searches are :hp2.not:ehp2. case-sensitive. .* Notes .* ************************************************************ :h2 res=7 id='notes'.Notes +:i1.Annotate :hp2.Adding and Using Notes:ehp2. :p. &dv. allows you to add notes (annotations) to your help @@ -568,7 +569,6 @@ you open a new or additional help file. .* ************************************************************ :h1 res=8 id='bookmarks'.Bookmarks :hp2.Bookmarks:ehp2. -:note.:hp8.*** This feature is partially implemented. ***:ehp8. :p. &dv. allows you to bookmark particular topics within the current help file. Simply click the bookmark toolbar button @@ -579,16 +579,16 @@ To jump to a bookmark, go to the "Bookmarks" menu, and click on the bookmark you want to open. :p. You can view or delete all your bookmarks by clicking on "Edit..." in -the "Bookmarks" menu. This window can remain open while you read, so -that you can quickly look through your bookmarks. +the "Bookmarks" menu. :p. Bookmarks are saved in a file with the extension ".bookmarks", in the &dv. config directory. This is in the user's home profile directory where there is read/write access. Under Linux it is normally "~/.config/docview/" and under Windows it is normally "C:\Documents and Settings\<user>\Local Settings\Application Data\docview". -I will probably add a setting in &dv., so the user can configure a -preferred storage location for bookmarks (eg: some users prefer it like OS/2's View program did, -by storing notes in the same directory as the help file). +.* TODO +.* I will probably add a setting in &dv., so the user can configure a +.* preferred storage location for bookmarks (eg: some users prefer it like OS/2's View program did, +.* by storing notes in the same directory as the help file). .* ************************************************************ @@ -817,55 +817,90 @@ file names), so the environment variable "MYHELP" is not equal to "myhelp". .* ************************************************************ .* Keyboard Shortcuts .* ************************************************************ -.* TODO -.* :h1 res=10 id='KeyboardShortcuts'. -.* Keyboard Shortcuts -.* :p.:hp2.Keyboard Shortcuts:ehp2. -.* :p.Most keyboard shortcuts are visible in the menu&comma. but a few are not&per. -.* The additional shortcuts are&colon. -.* :p.:hp2.Alt&plus.F4:ehp2. Exit -.* :p.:hp2.Ctrl&plus.C:ehp2. Copy selected text to clipboard -.* :p.:hp2.F7:ehp2. Back -.* :p.:hp2.F8:ehp2. Forward -.* :p.:hp2.Ctrl&plus.Left:ehp2. Back -.* :p.:hp2.F11:ehp2. Previous in contents -.* :p.:hp2.F12:ehp2. Next in contents -.* :p. -.* :p.:hp2.Shortcuts visible in the menu:ehp2. -.* :p.:hp2.Ctrl&plus.O:ehp2. Open files -.* :p.:hp2.Ctrl&plus.E:ehp2. Open files from help paths -.* :p.:hp2.Ctrl&plus.N:ehp2. Open a new window -.* :p.:hp2.Ctrl&plus.P:ehp2. Print topic -.* :p.:hp2.F3:ehp2. Exit -.* :p. -.* :p. -.* :p.:hp2.Ctrl&plus.A:ehp2. Select all text in topic -.* :p.:hp2.Ctrl&plus.Ins:ehp2. Copy selected text to clipboard -.* :p. -.* :p.:hp2.Ctrl&plus.F:ehp2. Find within current topic -.* :p.:hp2.Ctrl&plus.G:ehp2. Repeat last find -.* :p. -.* :p.:hp2.Ctrl&plus.S:ehp2. Open global search tool -.* :p. -.* :p.:hp2.Alt&plus.C:ehp2. Change to the contents tab -.* :p.:hp2.Alt&plus.I:ehp2. Change to the index tab -.* :p.:hp2.Alt&plus.S:ehp2. Change to the search tab -.* :p.:hp2.Alt&plus.N:ehp2. Change to the notes tab -.* :p.:hp2.Alt&plus.P:ehp2. Toggle the left panel &lpar.tabs&rpar. on and off -.* :p.:hp2.F5:ehp2. Expand all contents -.* :p.:hp2.F6:ehp2. Collapse all contents -.* :p. -.* :p.:hp2.Esc:ehp2. Back -.* :p.:hp2.Ctrl&plus.Right:ehp2. Forward -.* :p.:hp2.Ctrl&plus.Up:ehp2. Previous topic in contents -.* :p.:hp2.Ctrl&plus.Down:ehp2. Next topic in contents -.* :p. -.* :p.:hp2.Ctrl&plus.D:ehp2. Edit bookmarks -.* :p.:hp2.Ctrl&plus.B:ehp2. Bookmark current topic -.* :p. -.* :p.:hp2.Ctrl&plus.M:ehp2. Add note at cursor position -.* :p. -.* :p.:hp2.F1:ehp2. Help for &dv. +:h1 res=10 id='KeyboardShortcuts'.Keyboard Shortcuts +:p.:hp2.Keyboard Shortcuts:ehp2. +:p.Most keyboard shortcuts are visible in the menu, but a few are not. +Here is a list of keyboard shortcuts supported by &dv.: + +:table rules=vert frame=box cols='45 20'. +:row. +:c.:hp2.TASK:ehp2. +:c.:hp2.PRESS:ehp2. + +:row. +:c.---------- +:c.---------- + +:row. +:c.Open File +:c.Ctrl+O + +:row. +:c.Open Additional Files +:c.Ctrl+Shift+O + +:row. +:c.Open Special (eg: environment variable) +:c.Ctrl+L + +:row. +:c.Save Current Top +:c.Ctrl+S + +:row. +:c.Quit &dv. +:c.Ctrl+Q + +:row. +:c.Switch to Contents tab +:c.F5 + +:row. +:c.Switch to Index tab +:c.F6 + +:row. +:c.Switch to Search tab +:c.F7 + +:row. +:c.Switch to Notes tab +:c.F8 + +:row. +:c.Switch to History tab +:c.F9 + +:row. +:c.Navigate Back +:c.Ctrl+left arrow + +:row. +:c.Navigate Forward +:c.Ctrl+right arrow + +:row. +:c.Navigate to previous topic in contents +:c.Ctrl+up arrow + +:row. +:c.Navigate to next topic in contents +:c.Ctrl+down arrow + +:row. +:c.Bookmark current topic +:c.Ctrl+B + +:row. +:c.Edit Bookmarks +:c.Ctrl+M + +:row. +:c.Add note (annotations) at cursor position +:c.Ctrl+M + +:etable. + .* ************************************************************ diff --git a/docview/src/HelpBitmap.pas b/docview/src/HelpBitmap.pas index 0931ce82..692bf64d 100644 --- a/docview/src/HelpBitmap.pas +++ b/docview/src/HelpBitmap.pas @@ -1,14 +1,32 @@ +{ + fpGUI - Free Pascal GUI Toolkit + + Copyright (C) 2006 - 2013 See the file AUTHORS.txt, included in this + distribution, for details of the copyright. + + See the file COPYING.modifiedLGPL, included in this distribution, + for details about redistributing fpGUI. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Description: + Encapsulates a bitmap as stored in a IPF file. Once created from + file data they can be used as a normal bitmap. +} + unit HelpBitmap; {$mode objfpc}{$H+} -interface +// Debug purposes only +{.$define LZW_DEBUG} -// Encapsulates a bitmap as stored in a IPF file. -// Once created from file data they can be used as a normal bitmap. +interface uses - Classes, SysUtils, fpg_main, ctypes, + Classes, SysUtils, fpg_main, IPFFileFormatUnit; type @@ -65,7 +83,6 @@ type _UncompressedBlockSize: longint; function GetPaletteSize: longint; procedure BitmapError(Msg: string); - procedure DecompressLZW(var Buffer: Pointer; const Count: integer; var NewBuffer: PByte; var NewCount: integer); procedure ReadBitmapData( Blocks: TList; TotalSize: longint ); public constructor CreateFromHelpFile(var AFileHandle: TFileStream; Offset: longint); @@ -73,19 +90,13 @@ type end; -var - LZWDecompressBlock: function( pInput: PBYTE; - pOutput: PBYTE; - bytesIn: uint32; - Var bytesOut: uint32; - Var FinalCode: byte ): Boolean; -// APIENTRY; -// 'newview' index 1; - implementation uses - nvUtilities, Math, fpg_imgfmt_bmp; + nvUtilities, + Math, + LZWDecompress, + fpg_imgfmt_bmp; const BFT_bMAP =$4d62; // 'bM' @@ -102,9 +113,15 @@ type _Size: uint16; _CompressionType: uint8; _Data: PBYTE; + constructor Create; destructor Destroy; override; end; +constructor TBitmapBlock.Create; +begin + _Data := nil; +end; + destructor TBitmapBlock.Destroy; begin FreeMem( _Data ); @@ -119,7 +136,6 @@ var BytesRead: longint; Block: TBitmapBlock; - p: pointer; Blocks: TList; BlockIndex: longint; ImageType: uint16; @@ -154,7 +170,8 @@ begin if _Header.usType <> BFT_bMAP then raise EHelpBitmapException.Create( 'Invalid bitmap header' ); - _Header.usType := $4d42; // sibyl only accepts 'BM' not 'bM' +// Graeme: we don't need to do this any more. It was only for Sybil +// _Header.usType := $4d42; // sibyl only accepts 'BM' not 'bM' // We can only parse bitmaps with 1 colour plane // (I can't be bothered and have never seen bitmaps @@ -174,19 +191,19 @@ begin _BitsSize := LineSize * _Header.cy; // Correct header offset - it is wrong in the header (why?) - _Header.OffBits := sizeof( _Header ) + GetPaletteSize; // TODO: Graeme, double check this! + _Header.OffBits := sizeof( _Header ) + GetPaletteSize; // Load palette if _Header.cBitCount <= 8 then begin _pPalette := GetMem( GetPaletteSize ); - bytes := FileHandle.Read(_pPalette, GetPaletteSize); + bytes := FileHandle.Read(_pPalette^, GetPaletteSize); if bytes <> GetPaletteSize then raise EHelpBitmapException.Create( 'Failed to read Palette.' ); end; // Read data header - FillChar( DataHeader, sizeof( DataHeader ), 0 ); +// FillChar( DataHeader, sizeof( DataHeader ), 0 ); bytes := FileHandle.Read(DataHeader, SizeOf(DataHeader)); if bytes <> SizeOf(DataHeader) then raise EHelpBitmapException.Create( 'Failed to read DataHeader.' ); @@ -214,7 +231,7 @@ begin // Now read the block Block._Data := GetMem( Block._Size ); - FileHandle.Read(Block._Data, Block._Size); + FileHandle.Read(Block._Data^, Block._Size); inc( BytesRead, Block._Size ); Blocks.Add( Block ); @@ -250,227 +267,7 @@ begin inherited Destroy; end; -procedure THelpBitmap.DecompressLZW(var Buffer: Pointer; const Count: Integer; var NewBuffer: PByte; var NewCount: integer); -type - TLZWString = packed record - Count: integer; - Data: PByte; - end; - PLZWString = ^TLZWString; -const - ClearCode = 256; // clear table, start with 9bit codes - EoiCode = 257; // end of input -var -// NewBuffer: PByte; -// NewCount: PtrInt; - NewCapacity: PtrInt; - SrcPos: PtrInt; - SrcPosBit: integer; - CurBitLength: integer; - Code: Word; - Table: PLZWString; - TableCapacity: integer; - TableCount: integer; - OldCode: Word; - - function GetNextCode: Word; - var - v: Integer; - begin - Result:=0; - // CurBitLength can be 9 to 12 - //writeln('GetNextCode CurBitLength=',CurBitLength,' SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' ',hexstr(PByte(Buffer)[SrcPos],2),' ',hexstr(PByte(Buffer)[SrcPos+1],2),' ',hexstr(PByte(Buffer)[SrcPos+2],2)); - // read two or three bytes - if CurBitLength+SrcPosBit>16 then begin - // read from three bytes - if SrcPos+3>Count then BitmapError('LZW stream overrun'); - v:=PByte(Buffer)[SrcPos]; - inc(SrcPos); - v:=(v shl 8)+PByte(Buffer)[SrcPos]; - inc(SrcPos); - v:=(v shl 8)+PByte(Buffer)[SrcPos]; - v:=v shr (24-CurBitLength-SrcPosBit); - end else begin - // read from two bytes - if SrcPos+2>Count then BitmapError('LZW stream overrun'); - v:=PByte(Buffer)[SrcPos]; - inc(SrcPos); - v:=(v shl 8)+PByte(Buffer)[SrcPos]; - if CurBitLength+SrcPosBit=16 then - inc(SrcPos); - v:=v shr (16-CurBitLength-SrcPosBit); - end; - Result:=v and ((1 shl CurBitLength)-1); - SrcPosBit:=(SrcPosBit+CurBitLength) and 7; - //writeln('GetNextCode END SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' Result=',Result,' Result=',hexstr(Result,4)); - end; - - procedure ClearTable; - var - i: Integer; - begin - for i:=0 to TableCount-1 do - ReAllocMem(Table[i].Data,0); - TableCount:=0; - end; - - procedure InitializeTable; - begin - CurBitLength:=9; - ClearTable; - end; - - function IsInTable(Code: word): boolean; - begin - Result:=Code<258+TableCount; - end; - - procedure WriteStringFromCode(Code: integer; AddFirstChar: boolean = false); - var - s: TLZWString; - b: byte; - begin - //WriteLn('WriteStringFromCode Code=',Code,' AddFirstChar=',AddFirstChar,' x=',(NewCount div 4) mod IDF.ImageWidth,' y=',(NewCount div 4) div IDF.ImageWidth,' PixelByte=',NewCount mod 4); - if Code<256 then begin - // write byte - b:=Code; - s.Data:=@b; - s.Count:=1; - end else if Code>=258 then begin - // write string - if Code-258>=TableCount then - BitmapError('LZW code out of bounds'); - s:=Table[Code-258]; - end else - BitmapError('LZW code out of bounds'); - if NewCount+s.Count+1>NewCapacity then begin - NewCapacity:=NewCapacity*2+8; - ReAllocMem(NewBuffer,NewCapacity); - end; - System.Move(s.Data^,NewBuffer[NewCount],s.Count); - //for i:=0 to s.Count-1 do write(HexStr(NewBuffer[NewCount+i],2)); // debug - inc(NewCount,s.Count); - if AddFirstChar then begin - NewBuffer[NewCount]:=s.Data^; - //write(HexStr(NewBuffer[NewCount],2)); // debug - inc(NewCount); - end; - //writeln(',WriteStringFromCode'); // debug - end; - - procedure AddStringToTable(Code, AddFirstCharFromCode: integer); - // add string from code plus first character of string from code as new string - var - b1, b2: byte; - s1, s2: TLZWString; - p: PByte; - begin - //WriteLn('AddStringToTable Code=',Code,' FCFCode=',AddFirstCharFromCode,' TableCount=',TableCount,' TableCapacity=',TableCapacity); - // grow table - if TableCount>=TableCapacity then begin - TableCapacity:=TableCapacity*2+128; - ReAllocMem(Table,TableCapacity*SizeOf(TLZWString)); - end; - // find string 1 - if Code<256 then begin - // string is byte - b1:=Code; - s1.Data:=@b1; - s1.Count:=1; - end else if Code>=258 then begin - // normal string - if Code-258>=TableCount then - BitmapError('LZW code out of bounds'); - s1:=Table[Code-258]; - end else - BitmapError('LZW code out of bounds'); - // find string 2 - if AddFirstCharFromCode<256 then begin - // string is byte - b2:=AddFirstCharFromCode; - s2.Data:=@b2; - s2.Count:=1; - end else begin - // normal string - if AddFirstCharFromCode-258>=TableCount then - BitmapError('LZW code out of bounds'); - s2:=Table[AddFirstCharFromCode-258]; - end; - // set new table entry - Table[TableCount].Count:=s1.Count+1; - p:=nil; - GetMem(p,s1.Count+1); - Table[TableCount].Data:=p; - System.Move(s1.Data^,p^,s1.Count); - // add first character from string 2 - p[s1.Count]:=s2.Data^; - // increase TableCount - inc(TableCount); - case TableCount+259 of - 512,1024,2048: inc(CurBitLength); - 4096: BitmapError('LZW too many codes'); - end; - end; - -begin - if Count=0 then exit; - //WriteLn('TFPReaderTiff.DecompressLZW START Count=',Count); - //for SrcPos:=0 to 19 do - // write(HexStr(PByte(Buffer)[SrcPos],2)); - //writeln(); - - NewBuffer:=nil; - NewCount:=0; - NewCapacity:=Count*2; - ReAllocMem(NewBuffer,NewCapacity); - - SrcPos:=0; - SrcPosBit:=0; - CurBitLength:=9; - Table:=nil; - TableCount:=0; - TableCapacity:=0; - try - repeat - Code:=GetNextCode; - //WriteLn('TFPReaderTiff.DecompressLZW Code=',Code); - if Code=EoiCode then break; - if Code=ClearCode then begin - InitializeTable; - Code:=GetNextCode; - //WriteLn('TFPReaderTiff.DecompressLZW after clear Code=',Code); - if Code=EoiCode then break; - if Code=ClearCode then - BitmapError('LZW code out of bounds'); - WriteStringFromCode(Code); - OldCode:=Code; - end else begin - if Code<TableCount+258 then begin - WriteStringFromCode(Code); - AddStringToTable(OldCode,Code); - OldCode:=Code; - end else if Code=TableCount+258 then begin - WriteStringFromCode(OldCode,true); - AddStringToTable(OldCode,OldCode); - OldCode:=Code; - end else - BitmapError('LZW code out of bounds'); - end; - until false; - finally - ClearTable; - ReAllocMem(Table,0); - end; - - ReAllocMem(NewBuffer,NewCount); -// FreeMem(Buffer); -// Buffer:=NewBuffer; -// Count:=NewCount; -end; - - -procedure THelpBitmap.ReadBitmapData( Blocks: TList; - TotalSize: longint ); +procedure THelpBitmap.ReadBitmapData( Blocks: TList; TotalSize: longint ); var BytesWritten: longint; BytesWrittenFromBlock: longword; @@ -483,16 +280,22 @@ var BlockIndex: longint; BitmapData: PBYTE; ptr: PByte; + i: integer; + img: TfpgImage; begin + BitmapOutputPointer := nil; + BitmapData := nil; + ptr := nil; + // Allocate memory to store the bitmap - Bitmapdata := GetMem( TotalSize ); + BitmapData := GetMem( TotalSize ); // Copy header to bitmap - MemCopy( _Header, BitmapData, sizeof( _Header ) ); + MemCopy( _Header, BitmapData^, sizeof( _Header ) ); // Copy palette into bitmap ptr := BitmapData + sizeof( _Header ); - MemCopy( _pPalette, ptr, GetPaletteSize ); + MemCopy( _pPalette^, ptr^, GetPaletteSize ); BytesWritten := 0; @@ -506,23 +309,18 @@ begin case Block._CompressionType of 0,1: // uncompressed (I'm not sure about 1) begin - MemCopy( Block._Data, BitmapOutputPointer, Block._Size ); + MemCopy( Block._Data^, BitmapOutputPointer^, Block._Size ); BytesWrittenFromBlock := Block._Size; inc( BytesWritten, BytesWrittenFromBlock ); end; 2: // LZW compression begin - // decompress block - if not Assigned( LZWDecompressBlock )then - raise EHelpBitmapException.Create( 'Cannot decode bitmap - DLL not found' ); - -// DecompressLZW(Block._Data, Block._Size); - //LZWDecompressBlock( Block._Data, - // BitmapOutputPointer, - // Block._Size, - // BytesWrittenFromBlock, - // lastOutByte ); + LZWDecompressBlock( Block._Data, + Block._Size, + BitmapOutputPointer, + BytesWrittenFromBlock, + lastOutByte ); inc( BytesWritten, BytesWrittenFromBlock ); @@ -555,15 +353,37 @@ begin > BitmapData + TotalSize ) then assert( false ); - inc( BitmapOutputPointer, BytesWrittenFromBlock ); +{ NOTE: This doesn't seem right. It moves the pointer so later the moving of data to + ImageData will be wrong! } +// inc( BitmapOutputPointer, BytesWrittenFromBlock ); TPersistentObjectState end; + i := TotalSize + SizeOf(_Header) + GetPaletteSize; + img := CreateImage_BMP(BitmapData, i); AllocateImage(32, _Header.cx, _Header.cy); - if TotalSize <> ImageDataSize then - writeln('Warning: INF Bitmap size and allocated bitmap size are different. ', TotalSize, ' vs ', ImageDataSize); - Move(BitmapData^, ImageData^, TotalSize); + + {$IFDEF LZW_DEBUG} + writeln('Width = ', Width); + writeln('Height = ', Height); + writeln('ImageDataSize = ', ImageDataSize); + writeln('------------- START -------------'); + for i := 1 to ImageDataSize do + begin + write(HexStr(BitmapOutputPointer[i-1],2)+' '); + if (i mod 16 = 0) then + writeln('') + else if (i mod 4 = 0) then + write (' | '); + end; + Writeln(''); + writeln('------------- END -------------'); + {$ENDIF} + +// Move(BitmapOutputPointer^, ImageData^, ImageDataSize); + Move(img.ImageData^, self.ImageData^, img.ImageDataSize); UpdateImage; + img.Free; FreeMem( BitmapData, TotalSize ); end; diff --git a/docview/src/HelpFile.pas b/docview/src/HelpFile.pas index cc4657d9..ce3d9f8a 100644 --- a/docview/src/HelpFile.pas +++ b/docview/src/HelpFile.pas @@ -321,29 +321,6 @@ const entries.AddObject(anIndexEntry.getLabel, anIndexEntry); end; - - - -//Procedure OnLanguageEvent( Language: TLanguageFile; -// const Apply: boolean ); -//var -// tmpPrefix : String; -//begin -// tmpPrefix := 'HelpFile' + LANGUAGE_LABEL_DELIMITER; -// -// Language.LL( Apply, FileErrorNotFound, tmpPrefix + 'FileErrorNotFound', 'File not found' ); -// Language.LL( Apply, FileErrorAccessDenied, tmpPrefix + 'FileErrorAccessDenied', 'Access denied' ); -// Language.LL( Apply, FileErrorInUse, tmpPrefix + 'FileErrorInUse', 'File in use by another program' ); -// Language.LL( Apply, -// FileErrorInvalidHeader, -// tmpPrefix + 'FileErrorInvalidHeader', -// 'File doesn''t appear to be an OS/2 Help document (header ID not correct)' ); -// Language.LL( Apply, -// ErrorCorruptHelpFile, -// tmpPrefix + 'ErrorCorruptHelpFile', -// 'File is corrupt' ); -//end; - Function TopicFile( Topic: TTopic ): THelpFile; Begin Result := Topic.HelpFile as THelpFile; @@ -1119,9 +1096,7 @@ begin + ': ' + e.Message );} begin -// Bitmap := THelpBitmap.Create; Bitmap := THelpBitmap(fpgImages.GetImage('stdimg.dlg.critical')); -// Bitmap.LoadFromResourceName( 'MissingBitmap' ); // TODO: Add image resource to DocView end; end; diff --git a/docview/src/HelpTopic.pas b/docview/src/HelpTopic.pas index 5a8245a7..a9b981f1 100644 --- a/docview/src/HelpTopic.pas +++ b/docview/src/HelpTopic.pas @@ -231,7 +231,7 @@ implementation uses SysUtils - ,NewViewConstantsUnit + ,dvConstants ,nvUtilities ,ACLStringUtility ,SettingsUnit diff --git a/docview/src/NewViewConstantsUnit.pas b/docview/src/NewViewConstantsUnit.pas deleted file mode 100644 index 2aed1cd0..00000000 --- a/docview/src/NewViewConstantsUnit.pas +++ /dev/null @@ -1,37 +0,0 @@ -{ - fpGUI - Free Pascal GUI Toolkit - - Copyright (C) 2006 - 2010 See the file AUTHORS.txt, included in this - distribution, for details of the copyright. - - See the file COPYING.modifiedLGPL, included in this distribution, - for details about redistributing fpGUI. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - Description: - Common used constants for DocView -} -unit NewViewConstantsUnit; - -{$mode objfpc}{$H+} - -interface - -const - PARAM_LINK_NOTE = 'note'; - PARAM_LINK_PROGRAM = 'program'; - PARAM_LINK_URL = 'url'; - PARAM_LINK_EXTERNAL = 'external'; - - PRGM_EXPLORER = 'explore'; // web explorer - PRGM_NETSCAPE = 'netscape'; - PRGM_MOZILLA = 'mozilla'; - PRGM_FIREFOX = 'firefox'; - - -implementation - -end. diff --git a/docview/src/docview.lpi b/docview/src/docview.lpi index 30c8e7ff..6fc2c4cb 100644 --- a/docview/src/docview.lpi +++ b/docview/src/docview.lpi @@ -23,6 +23,7 @@ <RunParams> <local> <FormatVersion Value="1"/> + <CommandLineParams Value="/data/devel/tests/inf_test/test2.inf"/> <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> </local> </RunParams> @@ -31,7 +32,7 @@ <PackageName Value="fpgui_toolkit"/> </Item1> </RequiredPackages> - <Units Count="36"> + <Units Count="37"> <Unit0> <Filename Value="docview.lpr"/> <IsPartOfProject Value="True"/> @@ -132,80 +133,85 @@ <UnitName Value="HelpWindowDimensions"/> </Unit19> <Unit20> - <Filename Value="NewViewConstantsUnit.pas"/> + <Filename Value="SettingsUnit.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="NewViewConstantsUnit"/> + <UnitName Value="SettingsUnit"/> </Unit20> <Unit21> - <Filename Value="SettingsUnit.pas"/> + <Filename Value="dvconstants.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="SettingsUnit"/> + <UnitName Value="dvConstants"/> </Unit21> <Unit22> - <Filename Value="dvconstants.pas"/> + <Filename Value="dvHelpers.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="dvConstants"/> + <UnitName Value="dvHelpers"/> </Unit22> <Unit23> - <Filename Value="dvHelpers.pas"/> + <Filename Value="HelpWindow.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="dvHelpers"/> + <UnitName Value="HelpWindow"/> </Unit23> <Unit24> - <Filename Value="HelpWindow.pas"/> + <Filename Value="../TODO.txt"/> <IsPartOfProject Value="True"/> - <UnitName Value="HelpWindow"/> </Unit24> <Unit25> - <Filename Value="../TODO.txt"/> + <Filename Value="frm_configuration.pas"/> <IsPartOfProject Value="True"/> + <UnitName Value="frm_configuration"/> </Unit25> <Unit26> - <Filename Value="frm_configuration.pas"/> + <Filename Value="arrows.inc"/> <IsPartOfProject Value="True"/> - <UnitName Value="frm_configuration"/> </Unit26> <Unit27> - <Filename Value="arrows.inc"/> + <Filename Value="HelpBitmap.pas"/> <IsPartOfProject Value="True"/> + <UnitName Value="HelpBitmap"/> </Unit27> <Unit28> - <Filename Value="HelpBitmap.pas"/> + <Filename Value="missing.inc"/> <IsPartOfProject Value="True"/> - <UnitName Value="HelpBitmap"/> </Unit28> <Unit29> - <Filename Value="missing.inc"/> + <Filename Value="frm_text.pas"/> <IsPartOfProject Value="True"/> + <UnitName Value="frm_text"/> </Unit29> <Unit30> - <Filename Value="frm_text.pas"/> + <Filename Value="docview.rc"/> <IsPartOfProject Value="True"/> - <UnitName Value="frm_text"/> </Unit30> <Unit31> - <Filename Value="docview.rc"/> + <Filename Value="../docs/docview.ipf"/> <IsPartOfProject Value="True"/> </Unit31> <Unit32> - <Filename Value="../docs/docview.ipf"/> + <Filename Value="frm_note.pas"/> <IsPartOfProject Value="True"/> + <UnitName Value="frm_note"/> </Unit32> <Unit33> - <Filename Value="frm_note.pas"/> + <Filename Value="HelpNote.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="frm_note"/> + <UnitName Value="HelpNote"/> </Unit33> <Unit34> - <Filename Value="HelpNote.pas"/> + <Filename Value="HelpBookmark.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="HelpNote"/> + <UnitName Value="HelpBookmark"/> </Unit34> <Unit35> - <Filename Value="HelpBookmark.pas"/> + <Filename Value="frm_bookmarks.pas"/> <IsPartOfProject Value="True"/> - <UnitName Value="HelpBookmark"/> + <UnitName Value="frm_bookmarks"/> </Unit35> + <Unit36> + <Filename Value="lzwdecompress.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="LZWDecompress"/> + </Unit36> </Units> </ProjectOptions> <CompilerOptions> diff --git a/docview/src/docview.lpr b/docview/src/docview.lpr index e49aa4c3..0bee1dbe 100644 --- a/docview/src/docview.lpr +++ b/docview/src/docview.lpr @@ -7,12 +7,13 @@ uses {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} - Classes, fpg_main, frm_main, IPFEscapeCodes, HelpTopic, CompareWordUnit, SearchTable, - TextSearchQuery, nvUtilities, HelpFile, SearchUnit, fpg_cmdlineparams, - IPFFileFormatUnit, HelpWindowDimensions, NewViewConstantsUnit, SettingsUnit, + Classes, fpg_main, frm_main, IPFEscapeCodes, HelpTopic, CompareWordUnit, + SearchTable, TextSearchQuery, nvUtilities, HelpFile, SearchUnit, + fpg_cmdlineparams, IPFFileFormatUnit, HelpWindowDimensions, SettingsUnit, RichTextStyleUnit, CanvasFontManager, ACLStringUtility, RichTextDocumentUnit, RichTextView, RichTextLayoutUnit, RichTextDisplayUnit, dvconstants, dvHelpers, - frm_configuration, HelpBitmap, frm_text, frm_note, HelpNote, HelpBookmark; + frm_configuration, HelpBitmap, frm_text, frm_note, HelpNote, HelpBookmark, + frm_bookmarks, LZWDecompress; {$IFDEF WINDOWS} {$R docview.rc} diff --git a/docview/src/docview.rc b/docview/src/docview.rc index 87597a3f..34ed0c18 100644 --- a/docview/src/docview.rc +++ b/docview/src/docview.rc @@ -1,8 +1,8 @@ MAINICON ICON "../images/docview-48x48.ico" 1 VERSIONINFO -FILEVERSION 0, 8, 0, 0 -PRODUCTVERSION 0, 8, 0, 0 +FILEVERSION 1, 0, 0, 0 +PRODUCTVERSION 1, 0, 0, 0 FILEFLAGSMASK 0 FILEOS 0x40000 FILETYPE 1 @@ -13,12 +13,12 @@ FILETYPE 1 { VALUE "CompanyName", "fpGUI Toolkit" VALUE "FileDescription", "fpGUI's INF Documentation Viewer" - VALUE "FileVersion", "0.8.0" + VALUE "FileVersion", "1.0.0" VALUE "InternalName", "docview" VALUE "LegalCopyright", "GNU Public License" VALUE "OriginalFilename", "docview" VALUE "ProductName", "fpGUI Toolkit" - VALUE "ProductVersion", "0.8.0" + VALUE "ProductVersion", "1.0.0" } } BLOCK "VarFileInfo" diff --git a/docview/src/dvHelpers.pas b/docview/src/dvHelpers.pas index 28dc7809..2aaf710a 100644 --- a/docview/src/dvHelpers.pas +++ b/docview/src/dvHelpers.pas @@ -7,9 +7,6 @@ interface uses Classes, SysUtils, fpg_base; -const - OWN_HELP_MARKER = '[DOCVIEWHELP]'; - function GetOwnHelpFileName: String; // Given a filename, which may or may not contain a path or extension, diff --git a/docview/src/dvconstants.pas b/docview/src/dvconstants.pas index 9e0073be..e549bfe8 100644 --- a/docview/src/dvconstants.pas +++ b/docview/src/dvconstants.pas @@ -36,6 +36,7 @@ const NOTES_FILE_EXTENSION = ExtensionSeparator + 'notes'; BOOKMARK_FILE_EXTENSION = ExtensionSeparator + 'bookmark'; BOOKMARK_SECTION = '[BOOKMARK]'; + OWN_HELP_MARKER = '[DOCVIEWHELP]'; cDocViewHelpFile = 'docview.inf'; @@ -60,6 +61,18 @@ const hcConfigGeneralTab = 510; hcConfigFontsColorTab = 520; +const + PARAM_LINK_NOTE = 'note'; + PARAM_LINK_PROGRAM = 'program'; + PARAM_LINK_URL = 'url'; + PARAM_LINK_EXTERNAL = 'external'; + + PRGM_EXPLORER = 'explore'; // web explorer + PRGM_NETSCAPE = 'netscape'; + PRGM_MOZILLA = 'mozilla'; + PRGM_FIREFOX = 'firefox'; + + implementation diff --git a/docview/src/frm_bookmarks.pas b/docview/src/frm_bookmarks.pas new file mode 100644 index 00000000..4180b74f --- /dev/null +++ b/docview/src/frm_bookmarks.pas @@ -0,0 +1,312 @@ +unit frm_bookmarks; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, + Classes, + fpg_base, + fpg_main, + fpg_form, + fpg_listbox, + fpg_button, + HelpBookmark; + +type + TBookmarkCallback = procedure(Bookmark: TBookmark) of object; + + TBookmarksForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: BookmarksForm} + lbBookmarks: TfpgListBox; + btnRename: TfpgButton; + btnDelete: TfpgButton; + btnGoTo: TfpgButton; + btnHelp: TfpgButton; + btnClose: TfpgButton; + {@VFD_HEAD_END: BookmarksForm} + FBookmarkList: TList; + FOnBookmarksChanged: TNotifyEvent; + FOnGotoBookmark: TBookmarkCallback; + procedure lbBookmarksKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure FormShow(Sender: TObject); + procedure lbBookmarksDoubleClicked(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure btnRenameClicked(Sender: TObject); + procedure btnDeleteClicked(Sender: TObject); + procedure btnGotoClicked(Sender: TObject); + procedure btnHelpClicked(Sender: TObject); + procedure btnCloseClicked(Sender: TObject); + function SelectedObject(ListBox: TfpgListBox): TObject; + procedure UpdateControls; + function GetSelectedBookmark: TBookmark; + procedure GotoSelectedBookmark; + public + procedure AfterCreate; override; + procedure RefreshList; + published + property BookmarkList: TList read FBookmarkList write FBookmarkList; + property OnBookmarksChanged: TNotifyEvent read FOnBookmarksChanged write FOnBookmarksChanged; + property OnGotoBookmark: TBookmarkCallback read FOnGotoBookmark write FOnGotoBookmark; + end; + +{@VFD_NEWFORM_DECL} + +implementation + +uses + fpg_dialogs; + +{@VFD_NEWFORM_IMPL} + +procedure TBookmarksForm.lbBookmarksKeyPressed(Sender: TObject; + var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); +begin + if (KeyCode = keyEnter) or (KeyCode = keyPEnter) then + begin + GotoSelectedBookmark; + Close; + end; +end; + +procedure TBookmarksForm.FormShow(Sender: TObject); +begin + RefreshList; + lbBookmarks.SetFocus; +end; + +procedure TBookmarksForm.lbBookmarksDoubleClicked(Sender: TObject; + AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); +begin + GotoSelectedBookmark; + Close; +end; + +procedure TBookmarksForm.btnRenameClicked(Sender: TObject); +var + Bookmark: TBookmark; + lName: TfpgString; +begin + Bookmark := GetSelectedBookmark; + if Bookmark = nil then + exit; + + lName := Bookmark.Name; + if fpgInputQuery( 'Rename Bookmark', 'Enter the new name of the bookmark', lName ) then + begin + Bookmark.Name := lName; + if Assigned(OnBookmarksChanged) then + OnBookmarksChanged(self); + // redisplay name in list + lbBookmarks.Items[lbBookmarks.FocusItem] := Bookmark.Name; + lbBookmarks.Invalidate; + end; +end; + +procedure TBookmarksForm.btnDeleteClicked(Sender: TObject); +var + Bookmark: TBookmark; + BookmarkIndex: integer; +begin + Bookmark := GetSelectedBookmark; + if Bookmark = nil then + exit; + + if TfpgMessageDialog.Question('Delete Bookmark', + Format('Delete the bookmark named "%s"?', [Bookmark.Name])) = mbYes then + begin + BookmarkIndex := BookmarkList.IndexOf( Bookmark ); + lbBookmarks.Items.Delete( BookmarkIndex ); + BookmarkList.Delete( BookmarkIndex ); + + if BookmarkIndex > BookmarkList.Count - 1 then + BookmarkIndex := BookmarkList.Count - 1; + + lbBookmarks.FocusItem := BookmarkIndex; + + Bookmark.Free; + if Assigned(OnBookmarksChanged) then + OnBookmarksChanged(self); + lbBookmarks.Invalidate; + + UpdateControls; + end; +end; + +procedure TBookmarksForm.btnGotoClicked(Sender: TObject); +begin + GotoSelectedBookmark; +end; + +procedure TBookmarksForm.btnHelpClicked(Sender: TObject); +begin + InvokeHelp; +end; + +procedure TBookmarksForm.btnCloseClicked(Sender: TObject); +begin + Close; +end; + +function TBookmarksForm.SelectedObject(ListBox: TfpgListBox): TObject; +begin + if (ListBox.FocusItem >= 0) and (ListBox.FocusItem < ListBox.Items.Count) then + Result := ListBox.Items.Objects[ListBox.FocusItem] + else + Result := nil; +end; + +procedure TBookmarksForm.UpdateControls; +var + Selected: Boolean; +begin + Selected := GetSelectedBookmark <> nil; + btnRename.Enabled := Selected; + btnDelete.Enabled := Selected; + btnGoto.Enabled := Selected; + if not btnGoto.Enabled then + btnGoto.Default := false; +end; + +function TBookmarksForm.GetSelectedBookmark: TBookmark; +begin + if SelectedObject(lbBookmarks) = nil then + result := nil + else + result := SelectedObject(lbBookmarks) as TBookmark; +end; + +procedure TBookmarksForm.GotoSelectedBookmark; +begin + if Assigned(FOnGotoBookmark) then + if GetSelectedBookmark <> nil then + FOnGotoBookmark(GetSelectedBookmark); +end; + +procedure TBookmarksForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: BookmarksForm} + Name := 'BookmarksForm'; + SetPosition(553, 246, 393, 247); + WindowTitle := 'Bookmarks'; + Hint := ''; + HelpType := htContext; + HelpContext := 8; + OnShow := @FormShow; + + lbBookmarks := TfpgListBox.Create(self); + with lbBookmarks do + begin + Name := 'lbBookmarks'; + SetPosition(8, 12, 272, 227); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#List'; + Hint := ''; + TabOrder := 1; + OnDoubleClick := @lbBookmarksDoubleClicked; + OnKeyPress := @lbBookmarksKeyPressed; + end; + + btnRename := TfpgButton.Create(self); + with btnRename do + begin + Name := 'btnRename'; + SetPosition(288, 16, 96, 23); + Anchors := [anRight,anTop]; + Text := 'Rename...'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 2; + OnClick := @btnRenameClicked; + end; + + btnDelete := TfpgButton.Create(self); + with btnDelete do + begin + Name := 'btnDelete'; + SetPosition(288, 44, 96, 23); + Anchors := [anRight,anTop]; + Text := 'Delete'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 3; + OnClick := @btnDeleteClicked; + end; + + btnGoTo := TfpgButton.Create(self); + with btnGoTo do + begin + Name := 'btnGoTo'; + SetPosition(288, 72, 96, 23); + Anchors := [anRight,anTop]; + Text := 'Goto'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 4; + Default := True; + OnClick := @btnGotoClicked; + end; + + btnHelp := TfpgButton.Create(self); + with btnHelp do + begin + Name := 'btnHelp'; + SetPosition(288, 100, 96, 23); + Anchors := [anRight,anTop]; + Text := 'Help'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 5; + OnClick := @btnHelpClicked; + end; + + btnClose := TfpgButton.Create(self); + with btnClose do + begin + Name := 'btnClose'; + SetPosition(288, 217, 96, 23); + Anchors := [anRight,anBottom]; + Text := 'Close'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 6; + OnClick := @btnCloseClicked; + end; + + {@VFD_BODY_END: BookmarksForm} + {%endregion} +end; + +procedure TBookmarksForm.RefreshList; +var + i: integer; + Bookmark: TBookmark; +Begin + lbBookmarks.Items.BeginUpdate; + + lbBookmarks.Items.Clear; + + if not Assigned(BookmarkList) then + exit; + + for i := 0 to BookmarkList.Count - 1 do + begin + Bookmark := TBookmark(BookmarkList[i]); + lbBookmarks.Items.AddObject(Bookmark.Name, Bookmark); + end; + + if lbBookmarks.Items.Count > 0 then + lbBookmarks.FocusItem := 0; + + lbBookmarks.Items.EndUpdate; + UpdateControls; +End; + +end. diff --git a/docview/src/frm_main.pas b/docview/src/frm_main.pas index ddad88d8..97a9fd04 100644 --- a/docview/src/frm_main.pas +++ b/docview/src/frm_main.pas @@ -58,6 +58,7 @@ type RichView: TRichTextView; MainMenu: TfpgMenuBar; miFile: TfpgPopupMenu; + miActions: TfpgPopupMenu; miSettings: TfpgPopupMenu; miBookmarks: TfpgPopupMenu; miView: TfpgPopupMenu; @@ -129,9 +130,19 @@ type procedure miFileOpenAdditionalFileClicked(Sender: TObject); procedure miFileOpenSpecialClicked(Sender: TObject); procedure miFileCloseClicked(Sender: TObject); + procedure miActionsContentsClicked(Sender: TObject); + procedure miActionsIndexClicked(Sender: TObject); + procedure miActionsSearchClicked(Sender: TObject); + procedure miActionsNotesClicked(Sender: TObject); + procedure miActionsHistoryClicked(Sender: TObject); + procedure miActionsBackClicked(Sender: TObject); + procedure miActionsForwardClicked(Sender: TObject); + procedure miActionsPrevTopicClicked(Sender: TObject); + procedure miActionsNextTopicClicked(Sender: TObject); procedure miConfigureClicked(Sender: TObject); procedure miViewExpandAllClicked(Sender: TObject); procedure miViewCollapseAllClicked(Sender: TObject); + procedure miOpenBookmarksMenuClicked(Sender: TObject); procedure miBookmarksMenuItemClicked(Sender: TObject); procedure miHelpProdInfoClicked(Sender: TObject); procedure miHelpAboutFPGui(Sender: TObject); @@ -234,7 +245,6 @@ type procedure ClearBookmarks; procedure OnBookmarksChanged(Sender: TObject); procedure BuildBookmarksMenu; - procedure UpdateBookmarksDisplay; procedure NavigateToBookmark(Bookmark: TBookmark); public constructor Create(AOwner: TComponent); override; @@ -265,7 +275,7 @@ uses ,frm_configuration ,frm_text ,frm_note - ,NewViewConstantsUnit + ,frm_bookmarks ,CanvasFontManager ,HelpNote ,RichTextDocumentUnit @@ -298,6 +308,26 @@ begin end end; +procedure TMainForm.miActionsBackClicked(Sender: TObject); +begin + btnBack.Click; +end; + +procedure TMainForm.miActionsForwardClicked(Sender: TObject); +begin + btnFwd.Click; +end; + +procedure TMainForm.miActionsPrevTopicClicked(Sender: TObject); +begin + btnPrev.Click; +end; + +procedure TMainForm.miActionsNextTopicClicked(Sender: TObject); +begin + btnNext.Click; +end; + procedure TMainForm.Splitter1DoubleClicked(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); begin @@ -420,9 +450,12 @@ end; procedure TMainForm.RichViewClickLink(Sender: TRichTextView; Link: string); var + LinkDetails: TfpgString; LinkIndex: integer; lLink: THelpLink; lHelp: THelpFile; + f: THelpFile; + lHelpFileName: TfpgString; i: integer; lTopic: TTopic; lFound: Boolean; @@ -441,7 +474,32 @@ begin end else if pos(PARAM_LINK_EXTERNAL, Link) > 0 then begin - TfpgMessageDialog.Warning('', 'External links are not supported in DocView yet. Please try again with a later build.') + LinkDetails := StrRightFrom( Link, 10 ); // 10 is starting pos of data, after 'external ' + LinkIndex := StrToInt( ExtractNextValue( LinkDetails, ' ' ) ); + lHelp := CurrentTopic.HelpFile as THelpFile; + + lHelpFileName := lHelp.ReferencedFiles[ LinkIndex ]; + + { Only open the external file once. So see if it is already openned. } + lFound := False; + for i := 0 to CurrentOpenFiles.Count-1 do + begin + f := THelpFile(CurrentOpenFiles[i]); + if SameText(fpgExtractFileName(f.Filename), lHelpFileName) then + lFound := True; + end; + if not lFound then + begin + OpenAdditionalFile := True; + OpenFile(lHelpFileName, '', false); + OpenAdditionalFile := False; + end; + + { Not sure if we have an ID or Resource Name, so lets try both if possible } + if TryStrToInt(LinkDetails, i) then + DisplayTopicByResourceID(i) + else + DisplayTopicByName(LinkDetails); end else if pos(PARAM_LINK_URL, Link) > 0 then begin @@ -578,6 +636,31 @@ begin CloseFile; end; +procedure TMainForm.miActionsContentsClicked(Sender: TObject); +begin + PageControl1.ActivePage := tsContents; +end; + +procedure TMainForm.miActionsIndexClicked(Sender: TObject); +begin + PageControl1.ActivePage := tsIndex; +end; + +procedure TMainForm.miActionsSearchClicked(Sender: TObject); +begin + PageControl1.ActivePage := tsSearch; +end; + +procedure TMainForm.miActionsNotesClicked(Sender: TObject); +begin + PageControl1.ActivePage := tsNotes; +end; + +procedure TMainForm.miActionsHistoryClicked(Sender: TObject); +begin + PageControl1.ActivePage := tsHistory; +end; + procedure TMainForm.miConfigureClicked(Sender: TObject); begin ShowConfigForm; @@ -594,6 +677,21 @@ begin tvContents.FullCollapse; end; +procedure TMainForm.miOpenBookmarksMenuClicked(Sender: TObject); +var + frm: TBookmarksForm; +begin + frm := TBookmarksForm.Create(nil); + try + frm.BookmarkList := Bookmarks; + frm.OnGotoBookmark := @NavigateToBookmark; + frm.OnBookmarksChanged := @OnBookmarksChanged; + frm.ShowModal; + finally + frm.Free; + end; +end; + procedure TMainForm.miBookmarksMenuItemClicked(Sender: TObject); var t: PtrInt; @@ -2465,8 +2563,7 @@ begin if ImageIndices.Count > 0 then begin - { TODO -oGraeme : We do not support images yet } -// THelpFile(CurrentTopic.HelpFile).GetImages(ImageIndices, FImages); + THelpFile(CurrentTopic.HelpFile).GetImages(ImageIndices, FImages); end; ImageIndices.Free; @@ -2531,6 +2628,7 @@ var begin inherited Create(AOwner); fpgApplication.OnException := @MainFormException; + fpgApplication.HelpFile := cDocViewHelpFile; OnShow := @MainFormShow; OnDestroy := @MainFormDestroy; // Files := TList.Create; @@ -3078,17 +3176,34 @@ begin begin Name := 'miFile'; SetPosition(292, 96, 132, 20); - AddMenuItem('Open...', 'Ctrl+O', @miFileOpenClicked); - AddMenuItem('Open additional file...', 'Ctrl+Shift+O', @miFileOpenAdditionalFileClicked); - AddMenuItem('Open Special...', 'Ctrl+L', @miFileOpenSpecialClicked); - AddMenuItem('Save current Topic to IPF...', 'Ctrl+S', @miFileSaveTopicAsIPF); - AddMenuItem('Close', 'Ctrl+W', @miFileCloseClicked); - AddMenuitem('-', '', nil); + AddMenuItem('Open...', rsKeyCtrl+'O', @miFileOpenClicked); + AddMenuItem('Open additional file...', rsKeyCtrl+rsKeyShift+'O', @miFileOpenAdditionalFileClicked); + AddMenuItem('Open Special...', rsKeyCtrl+'L', @miFileOpenSpecialClicked); + AddMenuItem('Save current Topic to IPF...', rsKeyCtrl+'S', @miFileSaveTopicAsIPF); + AddMenuItem('Close', rsKeyCtrl+'W', @miFileCloseClicked); + AddSeparator; FFileOpenRecent := AddMenuItem('Open Recent...', '', nil); AddMenuitem('-', '', nil); AddMenuItem('Quit', 'Ctrl+Q', @miFileQuitClicked); end; + miActions := TfpgPopupMenu.Create(self); + with miActions do + begin + Name := 'miActions'; + SetPosition(282, 96, 132, 20); + AddMenuItem('Contents', 'F5', @miActionsContentsClicked); + AddMenuItem('Index', 'F6', @miActionsIndexClicked); + AddMenuItem('Search', 'F7', @miActionsSearchClicked); + AddMenuItem('Notes', 'F8', @miActionsNotesClicked); + AddMenuItem('History', 'F9', @miActionsHistoryClicked); + AddSeparator; + AddMenuItem('Back', rsKeyCtrl+'Left', @miActionsBackClicked); + AddMenuItem('Forward', rsKeyCtrl+'Right', @miActionsForwardClicked); + AddMenuItem('Previous Topic', rsKeyCtrl+'Up', @miActionsPrevTopicClicked); + AddMenuItem('Next Topic', rsKeyCtrl+'Down', @miActionsNextTopicClicked); + end; + miSettings := TfpgPopupMenu.Create(self); with miSettings do begin @@ -3102,8 +3217,10 @@ begin begin Name := 'miBookmarks'; SetPosition(292, 144, 132, 20); - AddMenuItem('Add..', '', nil).Enabled := False; - AddMenuItem('Show', '', nil).Enabled := False; + AddMenuItem('Add', rsKeyCtrl+'B', @btnBookmarkClick); + AddMenuItem('Edit...', rsKeyCtrl+'D', @miOpenBookmarksMenuClicked); + AddSeparator; + AddMenuItem('Add note at cursor position', rsKeyCtrl+'M', @btnNotesAddClick); end; miView := TfpgPopupMenu.Create(self); @@ -3113,7 +3230,7 @@ begin SetPosition(292, 216, 132, 20); AddMenuItem('Expand All', '', @miViewExpandAllClicked); AddMenuItem('Collapse All', '', @miViewCollapseAllClicked); - AddMenuItem('-', '', nil); + AddSeparator; AddMenuItem('Topic Properties', '', @miTopicPropertiesClicked); end; @@ -3137,9 +3254,9 @@ begin begin Name := 'miHelp'; SetPosition(292, 168, 132, 20); - AddMenuItem('Help using DocView', '', @miHelpUsingDocView); - AddMenuItem('Command line parameters', '', @miHelpCmdLineParams); - AddMenuItem('-', '', nil); + AddMenuItem('Help using DocView', rsKeyCtrl+'F1', @miHelpUsingDocView); + AddMenuItem('Command line parameters', rsKeyCtrl+rsKeyShift+'F1', @miHelpCmdLineParams); + AddSeparator; AddMenuItem('About fpGUI Toolkit...', '', @miHelpAboutFPGui); AddMenuItem('Product Information...', '', @miHelpProdInfoClicked); end; @@ -3375,6 +3492,7 @@ begin // hook up the sub-menus. MainMenu.AddMenuItem('&File', nil).SubMenu := miFile; MainMenu.AddMenuItem('&Settings', nil).SubMenu := miSettings; + MainMenu.AddMenuItem('&Actions', nil).SubMenu := miActions; MainMenu.AddMenuItem('&Bookmarks', nil).SubMenu := miBookmarks; MainMenu.AddMenuItem('&Tools', nil).SubMenu := miTools; MainMenu.AddMenuItem('&Help', nil).SubMenu := miHelp; @@ -3842,7 +3960,6 @@ end; procedure TMainForm.OnBookmarksChanged(Sender: TObject); begin BuildBookmarksMenu; -// UpdateBookmarksForm; SaveBookmarks; end; @@ -3870,33 +3987,6 @@ begin end; end; -procedure TMainForm.UpdateBookmarksDisplay; -var - i: integer; - Bookmark: TBookmark; -Begin -(* - BookmarksListBox.Items.BeginUpdate; - BookmarksListBox.Clear; - - if not Assigned( BookmarkList ) then - exit; - - for i := 0 to BookmarkList.Count - 1 do - begin - Bookmark := BookmarkList[ i ]; - BookmarksListBox.Items.AddObject( Bookmark.Name, - Bookmark ); - end; - - if BookmarksListBox.Items.Count > 0 then - BookmarksListBox.ItemIndex := 0; - - BookmarksListBox.Items.EndUpdate; - UpdateControls; -*) -end; - procedure TMainForm.NavigateToBookmark(Bookmark: TBookmark); begin DisplayTopic(Bookmark.ContentsTopic); diff --git a/docview/src/frm_note.pas b/docview/src/frm_note.pas index 2e280a89..310cc252 100644 --- a/docview/src/frm_note.pas +++ b/docview/src/frm_note.pas @@ -5,7 +5,13 @@ unit frm_note; interface uses - SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_memo, fpg_button; + SysUtils, + Classes, + fpg_base, + fpg_main, + fpg_form, + fpg_memo, + fpg_button; type @@ -23,6 +29,7 @@ type function GetText: TfpgString; procedure SetText(const AValue: TfpgString); procedure SetCanDelete(const AValue: boolean); + procedure btnHelpClicked(Sender: TObject); public procedure AfterCreate; override; property Text: TfpgString read GetText write SetText; @@ -56,6 +63,11 @@ begin btnDelete.Enabled := FCanDelete; end; +procedure TNoteForm.btnHelpClicked(Sender: TObject); +begin + InvokeHelp; +end; + procedure TNoteForm.AfterCreate; begin {%region 'Auto-generated GUI code' -fold} @@ -65,6 +77,8 @@ begin WindowTitle := 'Notes'; Hint := ''; OnShow := @FormShow; + HelpType := htContext; + HelpContext := 7; Memo1 := TfpgMemo.Create(self); with Memo1 do @@ -102,6 +116,7 @@ begin Hint := ''; ImageName := ''; TabOrder := 3; + OnClick := @btnHelpClicked; end; btnCancel := TfpgButton.Create(self); diff --git a/docview/src/lzwdecompress.pas b/docview/src/lzwdecompress.pas new file mode 100644 index 00000000..0ebba38c --- /dev/null +++ b/docview/src/lzwdecompress.pas @@ -0,0 +1,263 @@ +{ + fpGUI - Free Pascal GUI Toolkit + + Copyright (C) 2006 - 2013 See the file AUTHORS.txt, included in this + distribution, for details of the copyright. + + See the file COPYING.modifiedLGPL, included in this distribution, + for details about redistributing fpGUI. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Description: + LZW decompression code for uncompressing IPF bitmaps. +} + +unit LZWDecompress; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, types; + +procedure LZWDecompressBlock( pbInput: pByte; + number_bytes: LongWord; + pbOutput: PBYTE; + Var bytesOut: LongWord; + Var FinalCode: byte ); + +Implementation + +(* +/******************************************************************** + * * + * LZW decompression * + * * + *******************************************************************/ + +/* + * This is based on code (W) by Peter Fitzsimmons, pfitz@ican.net. + * His liner notes in the original: + * has its roots in a June 1990 + * DDJ article "LZW REVISITED", by Shawn M. Regan + * --=>revision history<=-- + * 1 lzw.c 21-Aug-96,2:24:36,`PLF' ; + * 2 lzw.c 24-Aug-96,2:27:24,`PLF' wip + * + * The code has been modified to take the input not from an + * open file, but from any memory region. For this, a double + * pointer is used, which must be passed to LZWDecompressBlock. + * I've also added a few comments for clarity. + * + * Ported to Sibyl Pascal by Aaron Lawrence + * Variables renamed etc to make things clearer. + */ +*) +// -- Stuff for LZW decompression -- */ +const INIT_BITS = 9; +const MAX_BITS = 12; //PLF Tue 95-10-03 02:16:56*/ +const HASHING_SHIFT = MAX_BITS - 8; + +{if MAX_BITS == 15 +const TABLE_SIZE 36768 +#elif MAX_BITS == 14 +const TABLE_SIZE 18041 +#elif MAX_BITS == 13 +const TABLE_SIZE 9029 +#else} +// For max_bits = 12: +const TABLE_SIZE = 5021; + +const CLEAR_TABLE = 256; +const TERMINATOR = 257; +const FIRST_CODE = 258; + +function MaxValNBits( N: word ): word; +begin + Result:= ( 1 shl n ) - 1; +end; + +var + prefix_code: array[ 0..TABLE_SIZE ] of longword; + append_character: array[ 0..TABLE_SIZE ] of Byte; + decode_stack: array[ 0..10000 ] of byte; + bitsPerCode: longint; + maxDictionaryCode: longint; + +(* + * decode_string: + * + *) +function decode_string( buffer: PByte; code: longword ): PByte; +var + i: longint; +begin + i:= 0; + + while Code > 255 do + begin + buffer^:= append_character[ Code ]; + inc( Buffer ); + code:= prefix_code[ code ]; + + inc( i ); + if i > High( decode_stack ) then + assert( false, 'Out of space decompressing bitmap!' ); + end; + + buffer^ := code; + Result:= buffer; +end; + +(* + * input_code: + * this function reads in bytes from the input + * stream. + *) + +var + bytes_out: longword = 0; + input_bit_count: longword = 0; + input_bit_buffer: longword = 0; + +// I think this simply reads the next bitsPerCode bits of the input data +// returning the resulting code. +function input_code( var pbInput: PBYTE; bytes_to_read: longword ): longword; +var + return_value: longword; +begin + while input_bit_count <= 24 do + begin + if bytes_out <= bytes_to_read then + begin + input_bit_buffer:= input_bit_buffer + or + ( ( longword( pbInput^ ) shl (24 - input_bit_count) ) ); + inc( pbInput ); + end + else + input_bit_buffer:= input_bit_buffer + or + ( longword( 0 ) shl ( 24 - input_bit_count ) ); + inc( bytes_out ); + inc( input_bit_count, 8 ); + end; + + return_value:= input_bit_buffer shr (32 - bitsPerCode); + input_bit_buffer:= input_bit_buffer shl bitsPerCode; + dec( input_bit_count, bitsPerCode ); + + if bytes_out > bytes_to_read then + begin + // flush static vars and quit */ + bytes_out:= 0; + input_bit_count:= 0; + input_bit_buffer:= 0; + Result:= TERMINATOR; + end + else + Result:= return_value; +end; + +// LZWDecompressBlock: +// this takes one of the INF bitmap blocks +// and decompresses it using LZW algorithms. + +procedure LZWDecompressBlock( pbInput: pByte; + number_bytes: LongWord; + pbOutput: PBYTE; + Var bytesOut: LongWord; + Var FinalCode: byte ); +var + nextAvailableCode: LongWord; + currentCode: LongWord; + lastCode: LongWord; + character: longword; + clear_flag: boolean; + theString: pByte; +begin + clear_flag:= true; + + nextAvailableCode:= FIRST_CODE; + bitsPerCode:= INIT_BITS; + maxDictionaryCode:= MaxValNBits( bitsPerCode ); + + bytesOut:= 0; + input_bit_count:= 0; + input_bit_buffer:= 0; + + // read the first code from input + currentCode:= input_code( pbInput, number_bytes ); + while currentCode <> TERMINATOR do + begin + if clear_flag then + begin + clear_flag:= false; + lastCode:= currentCode; + character:= currentCode; + + pbOutput^:= currentCode; + inc( pbOutput ); + FinalCode:= currentCode; + inc( BytesOut ); + end + else if currentCode = CLEAR_TABLE then + begin + clear_flag:= true; + nextAvailableCode:= FIRST_CODE; + bitsPerCode:= INIT_BITS; + maxDictionaryCode:= MaxValNBits( bitsPerCode ); + end + else + begin + if currentCode >= nextAvailableCode then + begin + decode_stack[ 0 ]:= character; + theString:= decode_string( Addr( decode_stack[ 1 ] ), + lastCode ); + end + else + theString:= decode_string( Addr( decode_stack[ 0 ] ), + currentCode ); + + character:= longword( theString^ ); + while theString >= Addr( decode_stack[ 0 ] ) do + begin + FinalCode:= theString^; + + pbOutput^:= theString^; + inc( pbOutput ); + dec( TheString ); + + inc( BytesOut ); + end; + + if nextAvailableCode <= maxDictionaryCode then + begin + prefix_code[ nextAvailableCode ]:= lastCode; + append_character[ nextAvailableCode ]:= character; + + inc( nextAvailableCode ); + + if ( nextAvailableCode = maxDictionaryCode ) and ( bitsPerCode < MAX_BITS ) then + begin + // expand dictionary + inc( bitsPerCode ); + maxDictionaryCode:= MaxValNBits( bitsPerCode ); + end; + end; + + lastCode:= currentCode; + end; + + // Read next code from input + currentCode:= input_code( pbInput, number_bytes ); + end; +end; + + +End. diff --git a/examples/apps/debugserver/fpgDebugServer.lpi b/examples/apps/debugserver/fpgDebugServer.lpi index da1b22f2..f26f35ee 100644 --- a/examples/apps/debugserver/fpgDebugServer.lpi +++ b/examples/apps/debugserver/fpgDebugServer.lpi @@ -54,7 +54,7 @@ <CompilerOptions> <Version Value="11"/> <Target> - <Filename Value="fpgDebugServer"/> + <Filename Value="dbugsrv"/> </Target> <SearchPaths> <UnitOutputDirectory Value="units/$(TargetCPU)-$(TargetOS)"/> diff --git a/examples/apps/docedit/docedit.lpi b/examples/apps/docedit/docedit.lpi index 640d3114..3e06fdd7 100644 --- a/examples/apps/docedit/docedit.lpi +++ b/examples/apps/docedit/docedit.lpi @@ -1,7 +1,7 @@ <?xml version="1.0"?> <CONFIG> <ProjectOptions> - <Version Value="7"/> + <Version Value="9"/> <General> <Flags> <SaveOnlyProjectUnits Value="True"/> @@ -9,11 +9,13 @@ </Flags> <SessionStorage Value="InProjectDir"/> <MainUnit Value="0"/> - <TargetFileExt Value=""/> </General> <VersionInfo> - <ProjectVersion Value=""/> + <StringTable ProductVersion=""/> </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="default" Default="True"/> + </BuildModes> <PublishOptions> <Version Value="2"/> <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> @@ -69,9 +71,19 @@ </Units> </ProjectOptions> <CompilerOptions> - <Version Value="8"/> + <Version Value="11"/> + <Target> + <Filename Value="docedit"/> + </Target> + <SearchPaths> + <UnitOutputDirectory Value="units"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> <Other> - <CustomOptions Value="-FUunits"/> <CompilerPath Value="$(CompPath)"/> </Other> </CompilerOptions> diff --git a/examples/apps/docedit/frm_main.pas b/examples/apps/docedit/frm_main.pas index f6404b79..884a2d5b 100644 --- a/examples/apps/docedit/frm_main.pas +++ b/examples/apps/docedit/frm_main.pas @@ -63,7 +63,9 @@ type implementation uses - fpg_dialogs, frm_options; + fpg_dialogs, + fpg_constants, + frm_options; const diff --git a/examples/corelib/aggcanvas/agg_canvas_test.lpi b/examples/corelib/aggcanvas/agg_canvas_test.lpi new file mode 100644 index 00000000..f02ac5df --- /dev/null +++ b/examples/corelib/aggcanvas/agg_canvas_test.lpi @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<CONFIG> + <ProjectOptions> + <Version Value="9"/> + <General> + <Flags> + <MainUnitHasCreateFormStatements Value="False"/> + <MainUnitHasTitleStatement Value="False"/> + <LRSInOutputDirectory Value="False"/> + </Flags> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <Title Value="agg_canvas_test"/> + </General> + <VersionInfo> + <StringTable ProductVersion=""/> + </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="fpgui_toolkit"/> + </Item1> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="agg_canvas_test.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="agg_canvas_test"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="agg_canvas_test"/> + </Target> + <SearchPaths> + <UnitOutputDirectory Value="units"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <Optimizations> + <OptimizationLevel Value="0"/> + </Optimizations> + </CodeGeneration> + <Other> + <CompilerMessages> + <UseMsgFile Value="True"/> + </CompilerMessages> + <CustomOptions Value="-dAggCanvas"/> + <CompilerPath Value="$(CompPath)"/> + </Other> + </CompilerOptions> +</CONFIG> diff --git a/examples/corelib/aggcanvas/agg_canvas_test.lpr b/examples/corelib/aggcanvas/agg_canvas_test.lpr new file mode 100644 index 00000000..63ba1d53 --- /dev/null +++ b/examples/corelib/aggcanvas/agg_canvas_test.lpr @@ -0,0 +1,389 @@ +{ + fpGUI - Free Pascal GUI Toolkit + + Copyright (C) 2006 - 2013 See the file AUTHORS.txt, included in this + distribution, for details of the copyright. + + See the file COPYING.modifiedLGPL, included in this distribution, + for details about redistributing fpGUI. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + Description: + This demo was simply to test the Agg2D unit and the TAgg2D canvas + class. In this demo the AggPas TAgg2D canvas does all the painting. + + *** IMPORTANT *** + For this demo to work, the fpGUI Toolkit MUST be compiled with + the AggCanvas compiler define enabled. +} + +program agg_canvas_test; + +{$mode objfpc}{$H+} + +uses + Classes, SysUtils, + fpg_base, + fpg_main, + fpg_form, + fpg_imgfmt_bmp, + fpg_widget, + Agg2D, + fpg_dialogs; + + +type + TMainForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: MainForm} + {@VFD_HEAD_END: MainForm} + procedure FormKeyPressed(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure FormPaint(Sender: TObject); + procedure CustomPaintJob; + public + procedure AfterCreate; override; + end; + + + +{ TMainForm } + +procedure TMainForm.FormKeyPressed(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if KeyCode = keyF1 then + begin + ShowMessage('This is a simple Canvas primitives painting test.'); + end; +end; + +procedure TMainForm.FormPaint(Sender: TObject); +begin + CustomPaintJob; +end; + +// We can now call all the paint methods from HandlePaint or +// the OnPaint event.fpgcanvas +procedure TMainForm.CustomPaintJob; +var + r: TfpgRect; + fnt: TfpgFont; + y: integer; + ac: TAgg2D; + c1, c2: TAggColor; + d: double; + i: integer; + + procedure DrawMacLionButton(const x, y: double; const astate: byte; const atext: ansistring); + { AState values are interpreted as follows: + 0: normal state + 1: hover state + 2: clicked state } + var + x1, y1, x2, y2, h, w: double; + c1, c2: TAggColor; + begin + h := 21; + w := 100; + x1 := x; + y1 := y; + x2 := x1 + w; + y2 := y1 + h; + + ac.NoLine; + + // Top vertical gradient + case astate of + 0: begin + c1.Construct(255, 255, 255, 255); + c2.Construct(243, 243, 243, 255); + end; + 1: begin + c1.Construct(204, 229, 252, 255); + c2.Construct(161, 209, 249, 255); + end; + 2: begin + c1.Construct(144, 195, 241, 255); + c2.Construct(113, 180, 239, 255); + end; + end; + ac.FillLinearGradient(x1, y1, x1, y1+(h/2), c1, c2); + ac.RoundedRect(x1+1, y1+1, x2+0.5, y2-(h/2)+0.5, 0.3, 3/2, 0.3, 3/2); + + // Bottom vertical gradient + case astate of + 0: begin + c1.Construct(236, 236, 236, 255); + c2.Construct(242, 242, 242, 255); + end; + 1: begin + c1.Construct(143, 202, 251, 255); + c2.Construct(207, 245, 253, 255); + end; + 2: begin + c1.Construct(97, 173, 240, 255); + c2.Construct(147, 206, 241, 255); + end; + end; + ac.FillLinearGradient(x1, y1+(h/2)+1, x1, y2, c1, c2); + ac.RoundedRect(x1+1, y1+(h/2)+0.5, x2+0.5, y2+0.5, 0.3, 3/2, 0.3, 3/2); + + // outer rounded rectangle + ac.NoFill; + case astate of + 0: begin + ac.LineColor($9a, $9a, $9a); + end; + 1, + 2: begin + ac.LineColor(86, 87, 143); + end; + end; + ac.LineWidth(1.0); + ac.RoundedRect(x1+0.5, y1+0.5, x2+0.5, y2+0.5, 3, 3); + + // Slight shadow under the button + ac.LineColor($df, $df, $df, $ff); + ac.Line(x1+0.5, y2+1.5, x2-0.5, y2+1.5); + + // button text + c1.Construct(0,0,0); + ac.FillColor(c1); + ac.NoLine; + ac.Font('arialbd.ttf', 13); + ac.TextAlignment(AGG_AlignCenter ,AGG_AlignCenter ); + ac.Text( + (x1 + x2 ) / 2.0 , + (y1 + y2 ) / 2.0 , + atext, + true ,0.0 ,0.0 ); + + end; +begin + // casting so we have full access to the Agg2D canvas functions + ac := TAgg2D(Canvas); + + ac.LineWidth(1.0); +// ac.ClearAll($ed, $ed, $ed); // mac os x window background color + ac.ClearAll(255, 255, 255); + + // Testing Rectangles + Canvas.SetColor(clBlack); + r.SetRect(0, 0, 1, 1); // 1x1 (this is really a dot) + Canvas.DrawRectangle(r); + Canvas.SetColor(clRed); + r.SetRect(0, 1, 1, 5); // 1x5 (this is really a vertical line) + Canvas.DrawRectangle(r); + Canvas.SetColor(clMagenta); + r.SetRect(1, 0, 5, 1); // 5x1 (this is really a horizontal line) + Canvas.DrawRectangle(r); + + Canvas.SetColor(clBlack); + r.Top := 5; + r.Left := 60; + r.Width := 50; + r.Height := 50; + Canvas.DrawRectangle(r); + + r.Left := 120; + Canvas.SetLineStyle(2, lsDash); + Canvas.DrawRectangle(r); + + r.Left := 180; + Canvas.SetColor(clGreen); + Canvas.SetLineStyle(1, lsDot); + Canvas.DrawRectangle(r); + + r.Left := 240; + Canvas.SetColor(clBlue); + Canvas.SetLineStyle(1, lsSolid); + Canvas.FillRectangle(r); + + // Testing line drawing + ac.NoFill; + Canvas.SetColor(clBlue); + Canvas.DrawLine(5, 5, 54, 54); + Canvas.DrawLine(54, 5, 5, 54); + Canvas.SetColor(clRed); + { Diagonal line } + r.SetRect(60, 5, 50, 50); + Canvas.DrawLine(r.Left, r.Top, r.Right, r.Bottom); + { Horizontal line } + Canvas.DrawLine(r.Left, r.Top-2, r.Right, r.Top-2); + { Vertical line } + Canvas.DrawLine(r.Left-2, r.Top, r.Left-2, r.Bottom); + + + // Testing Text and Fonts + y := 60; + Canvas.SetTextColor(clBlack); + Canvas.DrawString(5, y, 'This text must be black and default font'); + + // red dot indicates top/left corner of where previous text was started + Canvas.Pixels[5,y] := clRed; +// Canvas.DrawLine(5,y-4, 5,y+5); +// Canvas.DrawLine(1,y, 10, y); + + Canvas.SetTextColor(clRed); + y := y + Canvas.Font.Height; // fonts are different sizes on differet OS's + Canvas.DrawString(5, y, 'This text must be red.'); + Canvas.SetTextColor(clBlack); + y := y + Canvas.Font.Height; + Canvas.DrawString(5, y, 'Russian (UTF-8) text: ŠŠµŠ²Š¾Š·Š¼Š¾Š¶Š½Š¾ ŃŠ¾Š·Š“Š°ŃŃ Š“ŠøŃŠµŠŗŃŠ¾ŃŠøŃ'); + y := y + Canvas.Font.Height; + fnt := fpgApplication.GetFont('Times-14:bold'); + Canvas.Font := fnt; + Canvas.DrawString(5, y, 'Font used is ' + Canvas.Font.FontDesc); + y := y + Canvas.Font.Height; + + + // Testing basic style drawings + Canvas.Font := fpgApplication.DefaultFont; + Canvas.DrawString(320, 3, 'DrawButtonFace():'); + + r.SetRect(300, 20, 75, 25); + Canvas.DrawButtonFace(r, []); + Canvas.DrawString(385, 20, '= []'); + r.Top := 50; + Canvas.DrawButtonFace(r, [btfIsDefault]); + Canvas.DrawString(385, 50, '= [btnIsDefault]'); + r.Top := 80; + Canvas.DrawButtonFace(r, [btfIsPressed]); + Canvas.DrawString(385, 80, '= [btnIsPressed]'); + r.Top := 110; + Canvas.DrawButtonFace(r, [btfIsEmbedded, btfIsPressed]); + Canvas.DrawString(385, 110, '= [embed & press]'); + r.Top := 140; + Canvas.DrawButtonFace(r, [btfIsEmbedded]); + Canvas.DrawString(385, 140, '= [btnIsEmbedded]'); + + Canvas.DrawString(45, y, 'DrawControlFrame():'); + y := y + Canvas.Font.Height; + Canvas.DrawControlFrame(5, y, 150, 23); + + // A Vector Text example + //---------------------- + ac.LineWidth(1 ); + ac.TextAlignment(AGG_AlignLeft ,AGG_AlignBottom ); + // Normal Text + ac.LineColor($00 ,$00 ,$8B ); + ac.FillColor($1E ,$90 ,$FF ); + ac.Font('times.ttf' ,45); + ac.Text(20 ,250 , 'Vectors are cool !' ); + // Upside-down Text + ac.FlipText(true ); + ac.LineColor($C0 ,$C0 ,$C0 ); + ac.FillColor($C0 ,$C0 ,$C0 ); + ac.Text(20 ,255 ,'Vectors are cool !' ); + + // reset the text flip effect + ac.FlipText(False); + + // Cool new button styles are now possible + DrawMacLionButton(65, 300, 0, 'Normal'); + DrawMacLionButton(65, 330, 1, 'Hover'); + DrawMacLionButton(65, 360, 2, 'Clicked'); + // nice display of virtical text + c1.Construct(0,0,0); + ac.FillColor(c1); + ac.TextHints(true); + ac.NoLine; + ac.Font('arial.ttf', 13, false, + false, AGG_VectorFontCache, 45.0); + ac.Text(190, 310, 'Mac OS X Lion buttons', true ,0.0 ,0.0 ); + // reset text alignment to normal behaviour + ac.TextAlignment(AGG_AlignLeft ,AGG_AlignBottom ); + + + // Star shape + ac.LineCap(AGG_CapRound); + ac.LineWidth(5); + ac.LineColor($32 ,$cd ,$32 ); + c1.Construct(0, 0 , 255, 200); + c2.Construct(0, 0, 255, 50); + ac.FillLinearGradient(100, 100, 150, 150, c1, c2); + ac.Star(100 ,150 ,30 ,70 ,55 ,5 ); + + // Draw Arc from 45 degrees to 270 degrees + ac.LineColor($FF ,$00 ,$00 ); + ac.LineWidth(5 ); + ac.Arc(300 ,320 ,80 ,50 ,Deg2Rad(45 ) ,Deg2Rad(270 ) ); + + + // Lines at various thicknesses & coordinate translation + ac.LineColor(0, 0, 0); + ac.LineCap(AGG_CapRound); + d := 0.1; + for i := 1 to 10 do + begin + ac.LineWidth(d); + ac.Line(350, 240, 410, 180); + ac.Translate(8, 0); + d := d + 0.3; + end; + ac.ResetTransformations; + + + + ac.LineWidth(20 ); + ac.ResetPath; + ac.MoveTo(360 ,310 ); + ac.LineTo(400 ,270 ); + ac.LineTo(440 ,310 ); + + // Default AGG_JoinRound + ac.Translate(10 ,-10 ); + ac.DrawPath(AGG_StrokeOnly ); + + // Change to AGG_JoinMiter + ac.LineJoin (AGG_JoinMiter ); + ac.Translate(0 ,50 ); + ac.DrawPath (AGG_StrokeOnly ); + + // Change to AGG_JoinBevel + ac.LineJoin (AGG_JoinBevel ); + ac.Translate(0 ,50 ); + ac.DrawPath (AGG_StrokeOnly ); + + ac.ResetTransformations; +end; + +procedure TMainForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: MainForm} + Name := 'MainForm'; + SetPosition(357, 214, 500, 400); + WindowTitle := 'fpGUI with AGG-powered Canvas test'; + Hint := ''; + WindowPosition := wpOneThirdDown; + OnKeyPress := @FormKeyPressed; + OnPaint := @FormPaint; + + {@VFD_BODY_END: MainForm} + {%endregion} + + fpgApplication.HelpKey := keyNul; +end; + + +procedure MainProc; +var + frm: TMainForm; +begin + fpgApplication.Initialize; + + frm := TMainForm.Create(nil); + frm.Show; + fpgApplication.Run; + frm.Free; +end; + + +begin + MainProc; +end. + diff --git a/examples/corelib/aggcanvas/arial.ttf b/examples/corelib/aggcanvas/arial.ttf Binary files differnew file mode 100644 index 00000000..7ff88f22 --- /dev/null +++ b/examples/corelib/aggcanvas/arial.ttf diff --git a/examples/corelib/aggcanvas/arialbd.ttf b/examples/corelib/aggcanvas/arialbd.ttf Binary files differnew file mode 100644 index 00000000..c2eb3ddd --- /dev/null +++ b/examples/corelib/aggcanvas/arialbd.ttf diff --git a/examples/corelib/aggcanvas/extrafpc.cfg b/examples/corelib/aggcanvas/extrafpc.cfg new file mode 100644 index 00000000..775d592f --- /dev/null +++ b/examples/corelib/aggcanvas/extrafpc.cfg @@ -0,0 +1,5 @@ +-FUunits +-Fu../../../lib/$fpctarget +-Xs +-XX +-CX diff --git a/examples/corelib/aggcanvas/times.ttf b/examples/corelib/aggcanvas/times.ttf Binary files differnew file mode 100644 index 00000000..a998feec --- /dev/null +++ b/examples/corelib/aggcanvas/times.ttf diff --git a/examples/corelib/aggcanvas/timesi.ttf b/examples/corelib/aggcanvas/timesi.ttf Binary files differnew file mode 100644 index 00000000..28798d3c --- /dev/null +++ b/examples/corelib/aggcanvas/timesi.ttf diff --git a/prototypes/keyboard_symbols.txt b/prototypes/keyboard_symbols.txt new file mode 100644 index 00000000..1bcd48a0 --- /dev/null +++ b/prototypes/keyboard_symbols.txt @@ -0,0 +1,37 @@ + + + HTML Entity . .. GLYPH . . . NAME + + . . . . . ļ£æ . . . Apple + + ⌘ . . . . . ā . . . Command, Cmd, Clover, (formerly) Apple + ⌃ . . . . . ā . . . Control, Ctl, Ctrl + ⌥ . . . . . ā„ . . . Option, Opt, (Windows) Alt + ⇧ . . . . . ā§ . . . Shift + ⇪ . . . . . āŖ . . . Caps lock + ⏏ . . . . . ā . . . Eject + + ↩ . . . . . ā© . . . Return, Carriage Return + ↵ ↵ . āµ . . . Return, Carriage Return + ⏎ . . . . . ā . . . Return, Carriage Return + ⌤ . . . . . ā¤ . . . Enter + + ⌫ . . . . . ā« . . . Delete, Backspace + ⌦ . . . . . ā¦ . . . Forward Delete + ⎋ . . . . . ā . . . Escape, Esc + + → → .. ā . . . Right arrow + ← ← .. ā . . . Left arrow + ↑ ↑ .. ā . . . Up arrow + ↓ ↓ .. ā . . . Down arrow + ⇞ . . . . . ā . . . Page Up, PgUp + ⇟ . . . . . ā . . . Page Down, PgDn + ↖ . . . . . ā . . . Home + ↘ . . . . . ā . . . End + + ⌧ . . . . . ā§ . . . Clear + ⇥ . . . . . ā„ . . . Tab, Tab Right, Horizontal Tab + ⇤ . . . . . ā¤ . . . Shift Tab, Tab Left, Back-tab + ␢ . . . . . ā¢ . . . Space, Blank + ␣ . . . . . ā£ . .. . Space, Blank + diff --git a/src/VERSION_FILE.inc b/src/VERSION_FILE.inc index f5780fb3..212d4aea 100644 --- a/src/VERSION_FILE.inc +++ b/src/VERSION_FILE.inc @@ -1 +1 @@ -FPGUI_VERSION = '0.8'; +FPGUI_VERSION = '1.0'; diff --git a/src/corelib/fpg_base.pas b/src/corelib/fpg_base.pas index 5158540e..07d44191 100644 --- a/src/corelib/fpg_base.pas +++ b/src/corelib/fpg_base.pas @@ -22,6 +22,9 @@ unit fpg_base; // To enable the AggPas powered Canvas {.$define AGGCanvas} +// For debug use only +{.$define GDEBUG} + interface uses @@ -780,6 +783,9 @@ uses fpg_form, // needed for fpgApplication.CreateForms() typinfo, process, + {$IFDEF GDEBUG} + dbugintf, + {$ENDIF} dateutils; @@ -2617,7 +2623,9 @@ begin p.CommandLine := GetHelpViewer + ' ' + HelpFile else p.CommandLine := GetHelpViewer + ' ' + HelpFile + ' -n ' + IntToStr(AHelpContext); -//writeln('DEBUG: TfpgApplicationBase.ContextHelp > ', p.CommandLine); + {$ifdef GDEBUG} + senddebug(p.CommandLine); + {$endif} end else p.CommandLine := GetHelpViewer; @@ -2640,7 +2648,9 @@ begin if fpgFileExists(HelpFile) then begin p.CommandLine := GetHelpViewer + ' ' + HelpFile + ' -s ' + AHelpKeyword; -//writeln('DEBUG: TfpgApplicationBase.ContextHelp > ', p.CommandLine); + {$ifdef GDEBUG} + senddebug(p.CommandLine); + {$endif} end else p.CommandLine := GetHelpViewer; diff --git a/src/corelib/fpg_imgfmt_bmp.pas b/src/corelib/fpg_imgfmt_bmp.pas index 353b3216..ff354898 100644 --- a/src/corelib/fpg_imgfmt_bmp.pas +++ b/src/corelib/fpg_imgfmt_bmp.pas @@ -1,7 +1,7 @@ { fpGUI - Free Pascal GUI Toolkit - Copyright (C) 2006 - 2010 See the file AUTHORS.txt, included in this + Copyright (C) 2006 - 2013 See the file AUTHORS.txt, included in this distribution, for details of the copyright. See the file COPYING.modifiedLGPL, included in this distribution, @@ -39,6 +39,9 @@ uses fpg_utils; +procedure ReadImage_OS2BMP(img: TfpgImage; bmp: Pointer; bmpsize: longword); forward; + + function CreateImage_BMP(bmp: Pointer; bmpsize: longword): TfpgImage; begin Result := TfpgImage.Create; @@ -147,6 +150,13 @@ begin p := bmp; PByte(bh) := p; + + if bh^.signature = $4d62 then { 'bM' } + begin + ReadImage_OS2BMP(img, bmp, bmpsize); + exit; + end; + ppal := nil; if bh^.filesize <> bmpsize then Exit; //==> @@ -314,7 +324,229 @@ begin until linecnt >= img.Height; end; else - writeln('Unsupported BMP format!'); + raise Exception.Create('Unsupported BMP format!'); + end; + + if ppal <> nil then + FreeMem(ppal); + + img.UpdateImage; +end; + +type + { These records come from the HelpBitmap unit - part of DocView } + INFBITMAPHEADER = packed record + // BITMAP FILE HEADER + usType: uint16; // = 'bM' + cbSize: uint32; + xHotspot: uint16; + yHotspot: uint16; + DataOffset: uint32; // =size(hdr)+size(palette) + // BITMAP INFO HEADER + cbFIx: uint32; // =size(info_hdr) (usually = 12?) + Width: uint16; // width size + Height: uint16; // height size + cPlanes: uint16; // planes, =1 (always seems to be one) + BitCount: uint16; // bits per pixel + // followed by RGB triples if <= 8bpp + end; + + +procedure ReadImage_OS2BMP(img: TfpgImage; bmp: Pointer; bmpsize: longword); +var + bh: ^INFBITMAPHEADER; + p: PByte; + ppal: plongword; + pcol: Plongword; + palsize: integer; + pdata: PByte; + b: byte; + bit: byte; + bcnt: byte; + linecnt: integer; + pixelcnt: integer; + pdest: Plongword; + depth: integer; + + function GetPalColor(cindex: longword): longword; + var + pc: Plongword; + begin + pc := ppal; + Inc(pc, cindex); + Result := pc^; + end; + +begin + if img = nil then + Exit; //==> + + img.FreeImage; + + ppal := nil; + p := bmp; + PByte(bh) := p; + + pdata := bmp; + Inc(pdata, bh^.DataOffset); + Inc(p, SizeOf(INFBITMAPHEADER)); + + depth := bh^.BitCount; + + if depth > 1 then + img.AllocateImage(32, bh^.Width, bh^.Height)// color image (RGB or Indexed) + else + begin + img.AllocateImage(1, bh^.Width, bh^.Height); + img.AllocateMask; + end; + + if depth <= 8 then + begin + // reading color palette + case depth of + 1: palsize := 2; + 4: palsize := 16; + else + palsize := 256; + end; + + GetMem(ppal, palsize * SizeOf(longword)); + + pcol := ppal; + pixelcnt := 0; + // OS/2 1.x bitmap with uses 3-byte palette + while (p) < (pdata) do + begin + pcol^ := (LongWord($FF) shl 24) + (LongWord(p[2]) shl 16) + (LongWord(p[1]) shl 8) + LongWord(p[0]); + Inc(pcol); + inc(p, 3); + Inc(pixelcnt); + end; + end; + + pdest := img.ImageData; + Inc(pdest, img.Width * (img.Height - 1)); // bottom-up line order + p := bmp; + Inc(p, bh^.DataOffset); + + // reading the data... + case depth of + 1: + begin + // direct line transfer + //writeln('reading 1-bit color bitmap'); + linecnt := 0; + bcnt := img.Width div 32; + if (img.Width and $1F) > 0 then + Inc(bcnt); + + pdest := img.ImageData; + Inc(pdest, bcnt * (img.Height - 1)); // bottom-up line order + repeat + move(p^, pdest^, bcnt * 4); + Inc(p, bcnt * 4); + Dec(pdest, bcnt); + Inc(linecnt); + until linecnt >= img.Height; + + //Writeln(linecnt,' lines loaded.'); + move(img.ImageData^, img.MaskData^, img.ImageDataSize); + img.Invert(True); + end; + + 4: + begin + //writeln('reading 4-bit color'); + linecnt := 0; + repeat + // parse one line.. + bit := 0; + pixelcnt := 0; + bcnt := 0; + repeat + if bit = 0 then + b := (p^ shr 4) and $0F + else + begin + b := p^ and $0F; + Inc(p); + Inc(bcnt); + end; + + pdest^ := GetPalColor(b); + Inc(pdest); + Inc(pixelcnt); + bit := bit xor 1; + until pixelcnt >= img.Width; + + while (bcnt mod 4) <> 0 do + begin + Inc(bcnt); + Inc(p); + end; + + Inc(linecnt); + Dec(pdest, img.Width * 2); // go to next line + until linecnt >= img.Height; + end; + + 8: + begin + //writeln('reading 8-bit color'); + linecnt := 0; + repeat + // parse one line.. + pixelcnt := 0; + repeat + pdest^ := GetPalColor(p^); + Inc(p); + Inc(pdest); + Inc(pixelcnt); + until pixelcnt >= img.Width; + + while (pixelcnt mod 4) <> 0 do + begin + Inc(pixelcnt); + Inc(p); + end; + + Inc(linecnt); + Dec(pdest, img.Width * 2); // go to next line + until linecnt >= img.Height; + end; + + 24: + begin + //writeln('reading truecolor'); + linecnt := 0; + repeat + // parse one line.. + pixelcnt := 0; + repeat + pdest^ := p^; + Inc(p); + pdest^ := pdest^ or (longword(p^) shl 8); + Inc(p); + pdest^ := pdest^ or (longword(p^) shl 16) or ($FF shl 24) {alpha set to full opaque}; + Inc(p); + Inc(pdest); + Inc(pixelcnt); + until pixelcnt >= img.Width; + + pixelcnt := img.Width * 3; + while (pixelcnt mod 4) <> 0 do + begin + Inc(pixelcnt); + Inc(p); + end; + + Inc(linecnt); + Dec(pdest, img.Width * 2); // go to next line + until linecnt >= img.Height; + end; + else + raise Exception.Create('Unsupported BMP format!'); end; if ppal <> nil then diff --git a/src/corelib/fpg_imgfmt_png.pas b/src/corelib/fpg_imgfmt_png.pas index c0659d2e..c95150e4 100644 --- a/src/corelib/fpg_imgfmt_png.pas +++ b/src/corelib/fpg_imgfmt_png.pas @@ -1,7 +1,7 @@ { fpGUI - Free Pascal GUI Toolkit - Copyright (C) 2006 - 2012 See the file AUTHORS.txt, included in this + Copyright (C) 2006 - 2013 See the file AUTHORS.txt, included in this distribution, for details of the copyright. See the file COPYING.modifiedLGPL, included in this distribution, @@ -31,6 +31,7 @@ uses function LoadImage_PNG(const AFileName: TfpgString): TfpgImage; overload; function LoadImage_PNG(AStream: TStream): TfpgImage; overload; +function LoadImage_PNG(const AImageData: Pointer; const AImageDataSize: LongWord): TfpgImage; overload; function LoadImage_PNG(AInstance: THandle; const AResName: String; AResType: PChar): TfpgImage; overload; function LoadImage_PNGcrop(const AMaxWidth, AMaxHeight: integer; const AFileName: TfpgString): TfpgImage; @@ -114,6 +115,19 @@ begin end; end; +function LoadImage_PNG(const AImageData: Pointer; const AImageDataSize: LongWord): TfpgImage; +var + s: TMemoryStream; +begin + s := TMemoryStream.Create; + try + s.Write(AImageData^, AImageDataSize); + Result := LoadImage_PNG(s); + finally + s.Free; + end; +end; + function LoadImage_PNG(AInstance: THandle; const AResName: String; AResType: PChar): TfpgImage; var res: TResourceStream; diff --git a/src/corelib/fpg_widget.pas b/src/corelib/fpg_widget.pas index 67206557..a74c1b30 100644 --- a/src/corelib/fpg_widget.pas +++ b/src/corelib/fpg_widget.pas @@ -960,7 +960,7 @@ begin dir := 0; - if not consumed and (keycode = fpgApplication.HelpKey) then + if not consumed and (keycode = fpgApplication.HelpKey) and (shiftstate=[]) then begin InvokeHelp; consumed := True; diff --git a/src/corelib/render/software/Agg2D.pas b/src/corelib/render/software/Agg2D.pas index 280736af..50a68fb8 100644 --- a/src/corelib/render/software/Agg2D.pas +++ b/src/corelib/render/software/Agg2D.pas @@ -40,51 +40,51 @@ interface {$ENDIF} uses - agg_basics , - agg_array , - agg_trans_affine , - agg_trans_viewport , - agg_path_storage , - agg_conv_stroke , - agg_conv_transform , - agg_conv_curve , - agg_conv_dash , - agg_rendering_buffer , - agg_renderer_base , - agg_renderer_scanline , - agg_span_gradient , - agg_span_image_filter_rgba , - agg_span_image_resample_rgba , - agg_span_converter , - agg_span_interpolator_linear , - agg_span_allocator , - agg_rasterizer_scanline_aa , - agg_gamma_functions , - agg_scanline_u , - agg_arc , - agg_bezier_arc , - agg_rounded_rect , - agg_font_engine , - agg_font_cache_manager , - agg_pixfmt , - agg_pixfmt_rgb , - agg_pixfmt_rgba , - agg_color , - agg_math_stroke , - agg_image_filters , - agg_vertex_source , - agg_render_scanlines , + agg_basics , + agg_array , + agg_trans_affine , + agg_trans_viewport , + agg_path_storage , + agg_conv_stroke , + agg_conv_transform , + agg_conv_curve , + agg_conv_dash , + agg_rendering_buffer , + agg_renderer_base , + agg_renderer_scanline , + agg_span_gradient , + agg_span_image_filter_rgba , + agg_span_image_resample_rgba , + agg_span_converter , + agg_span_interpolator_linear , + agg_span_allocator , + agg_rasterizer_scanline_aa , + agg_gamma_functions , + agg_scanline_u , + agg_arc , + agg_bezier_arc , + agg_rounded_rect , + agg_font_engine , + agg_font_cache_manager , + agg_pixfmt , + agg_pixfmt_rgb , + agg_pixfmt_rgba , + agg_color , + agg_math_stroke , + agg_image_filters , + agg_vertex_source , + agg_render_scanlines , {$IFDEF AGG2D_USE_FREETYPE } - agg_font_freetype , + agg_font_freetype , {$ENDIF } {$IFDEF AGG2D_USE_WINFONTS} - agg_font_win32_tt , + agg_font_win32_tt , {$ENDIF } - Math , - Classes, - SysUtils, + Math, + Classes, + SysUtils, // This allows for platform specific uses clauses {$define uses_interface} @@ -98,8 +98,8 @@ uses {$I agg_platform_x11.inc} {$ENDIF} - fpg_base, - fpg_main; + fpg_base, + fpg_main; { GLOBAL VARIABLES & CONSTANTS } const diff --git a/src/gui/fpg_menu.pas b/src/gui/fpg_menu.pas index 8da9302f..91db5992 100644 --- a/src/gui/fpg_menu.pas +++ b/src/gui/fpg_menu.pas @@ -135,6 +135,7 @@ type destructor Destroy; override; procedure Close; override; function AddMenuItem(const AMenuName: TfpgString; const hotkeydef: string; OnClickProc: TNotifyEvent): TfpgMenuItem; + procedure AddSeparator; function MenuItemByName(const AMenuName: TfpgString): TfpgMenuItem; function MenuItem(const AMenuPos: integer): TfpgMenuItem; // added to allow for localization property BeforeShow: TNotifyEvent read FBeforeShow write FBeforeShow; @@ -1406,6 +1407,11 @@ begin end; end; +procedure TfpgPopupMenu.AddSeparator; +begin + AddMenuitem('-', '', nil); +end; + function TfpgPopupMenu.MenuItemByName(const AMenuName: TfpgString): TfpgMenuItem; var i: integer; diff --git a/src/gui/fpg_tree.pas b/src/gui/fpg_tree.pas index 4a4316ac..8935ec36 100644 --- a/src/gui/fpg_tree.pas +++ b/src/gui/fpg_tree.pas @@ -1868,74 +1868,77 @@ var OldSelection: TfpgTreeNode; begin OldSelection := Selection; - case KeyCode of - keyRight: - begin - Consumed := True; - Selection.Expand; - DoExpand(Selection); - ResetScrollbar; - RePaint; - end; + if ShiftState = [] then + begin + case KeyCode of + keyRight: + begin + Consumed := True; + Selection.Expand; + DoExpand(Selection); + ResetScrollbar; + RePaint; + end; - keyLeft: - begin - Consumed := True; - Selection.Collapsed := true; - ResetScrollbar; - RePaint; - end; + keyLeft: + begin + Consumed := True; + Selection.Collapsed := true; + ResetScrollbar; + RePaint; + end; - keyUp: - begin - if Selection = nil then - Selection := RootNode.FirstSubNode - else - if Selection <> RootNode then + keyUp: + begin + if Selection = nil then + Selection := RootNode.FirstSubNode + else + if Selection <> RootNode then + begin + if NodeIsVisible(selection) then + begin + h := PrevVisualNode(Selection); + if (h <> RootNode) and (h <> nil) then + Selection := h; + end + else + begin + Selection := RootNode.FirstSubNode; + end; + end; + Consumed := True; + end; + + keyDown: + begin + Consumed := True; + if Selection = nil then + Selection := RootNode.FirstSubNode + else begin if NodeIsVisible(selection) then begin - h := PrevVisualNode(Selection); - if (h <> RootNode) and (h <> nil) then + h := NextVisualNode(Selection); + if (h <> nil) then Selection := h; end else - begin Selection := RootNode.FirstSubNode; - end; end; - Consumed := True; - end; + end; - keyDown: - begin - Consumed := True; - if Selection = nil then - Selection := RootNode.FirstSubNode - else + keyPageUp: begin - if NodeIsVisible(selection) then - begin - h := NextVisualNode(Selection); - if (h <> nil) then - Selection := h; - end - else - Selection := RootNode.FirstSubNode; + FVScrollbar.PageUp; end; - end; - - keyPageUp: - begin - FVScrollbar.PageUp; - end; - keyPageDown: - begin - FVScrollbar.PageDown; - end; - else - Consumed := False; + keyPageDown: + begin + FVScrollbar.PageDown; + end; + else + Consumed := False; + end; end; if Selection <> OldSelection then |