Why Emacs is better editor, part two

In my previous article Why Emacs is better editor - a case study for javascript developer, I proved that Emacs is better than Sublime Text.

At least for this so-called "Goto Symbol" feature, Emacs wins.

It's because we use a plugin js2-mode. It's actually a javascript parser which creates the symbols from AST.

But in real world, sometimes regular expression could be better.

If you use those popular MVC javascript frameworks (Angular, for example), you will meet below code,

app.controller('MyController', function ($scope, $http) {
  // ...
});

In above case, using regular expression to extract the string "MyController" is more versatile and simpler.

My latest contribution to js2-mode solves this problem perfectly. It combines the powers of AST and regular expression.

The js2-mode will integrate this feature soon. I will notify you when next version is released.

Let's cut off the boring technical details and see the demo,

For a simple hello.js with below content,

function helloworld() {
  console.log('hello called');
}

function test() {
  console.log('test called');
}

app.controller('MyController', function ($scope, $http) {
  console.log('MyController registered');

  var that = this;

  $scope.test1 = function() {
    console.log('$scope.test called');
  };

  $scope.hello = function() {
    console.log('$scope.hello called');
  };

  $scope.fn1 = function () {

    function test() {
      console.log('hello world');
    };
    console.log('hello');
  };
});

Emacs: https://cloud.githubusercontent.com/assets/184553/4871228/3ebe6588-61a5-11e4-9e9f-e6eb8218e2ee.png

Sublime3 (build 3047): https://cloud.githubusercontent.com/assets/184553/4871347/c33b8fee-61ae-11e4-8072-671935b68d6b.png

Please note Emacs will display two functions with the same name "test" correctly!

BTW, my previous "Why Emacs is better" article got many feedbacks from Sublime users.

One feedback is that my comparison is not fair because I'm comparing Emacs plugin with naked Sublime. Though I did some research before writing the article, I could be wrong. Please enlighten me if you know such Sublime plugins.

Sublime users also argue that Sublime3 uses Python. Python is a better programming language. I'm qualified to answer this question because I wrote some large commercial Python application when I worked in Kodak R&D. First version I used was v2.2. So I've got about 10 years experience in Python. And I write lots of Emacs Lisp code these days. My opinion is that both languages are good enough as DSL for text editors. In Python, you can use OO. In Emacs Lisp, you can treat function as object and there are Macros and Advising. Enough widgets to shoot yourself in the foot in both languages. Python are surely newbie-friendly. But number of newbies doesn't matter in high-end rival.

My Emacs skill is improved after 3 years

This is my note on useful commands/keybindings to memorize three years ago.

Most are obsolete because I'm more skillful now.

Basically I use fewer but more powerful plugins. I write Emacs lisp if there is no suitable plugin.

  • Three years ago, column edit,
C-x r t yourstring RET (See "How to do select column then do editing in GNU Emacs ?"")

Now I use Evil

  • Three years ago, save current position to register and jump to the position,
C-r SPC to save, C-j to jump (better-registers.el required) 

Now Evil.

  • Three years ago, save frame configuration to register,
C-r f (better-registers.el required) 

Now workgroups2.

  • Three year ago, (un)comment line,
M-; (qiang-comment-dwim-line required)

Now evil-nerd-commenter.

  • Three years ago for visiting the next/previous error message after compiling,
"M-g M-n" or "M-g M-p"

I'm still using it.

  • Three years ago, find-tag/pop-tag-mark
"M-." and "M-*"

Now Evil

  • Three years ago, grep current work directory only or all sub-directories
M-x lgrep/rgrep

Now grep in shell plus percol

  • Three years ago, visit multiple tags table
M-x visit-tags-table

hack tags-table-list directly might be simpler.

  • Three years ago, set countdown timer
M-x org-timer-set-timer, C-c C-x ;

Now I don't push myself with the timer thing.

  • Three years ago, mark subtree in org-mode
M-x org-mark-subtree

It was used to select the text to post to my blog.

Now I use org2nikola. The org-mark-subtree is hardcoded into org2nikola.

How to accept the github pull request efficiently

I use keyboard only in order to maximize my productivity.

Preparation

Install Firefox addon Keysnail and its plugin HOK. From now on, all the web browsing is done ONLY in keyboard.

Step 1

Open pull request page at github.com, click the link "command line".

In a real world project, I rarely accept a pull request without some modification. So I usually avoid pressing the big green button "merge pull request" on that page.

In the "command line" page, github is kind enough to list the command lines to "check out a new branch and test the changes" from the pull request. The command lines are like:

git checkout -b her-master master
git pull git@github.com:her/myproject.git master

I have installed a Greasemonkey user script NinjaWebCoder in order to use keyboard to copy those command lines from the browser into clipboard. Then I paste the command lines into terminal.

Step 2

I open the code file with Emacs.

There is an Emacs addon called git-gutter. I use its command "git-gutter:next-hunk" to move the cursor to the next "diff hunk".

Let me explain what's the diff hunk. When you edit some code under git's control, you code has some difference with the HEAD version. Every difference corresponds to the pair of a file name and a line number. That file-name-line-number pair is defined as a "diff hunk".

Now comes the most important part of this article. Since version 0.71, git-gutter added a new command "git-gutter:set-start-version". If I "git-gutter:set-start-revision" to the "HEAD^" version. I can jump to "diff hunk" of the difference between "HEAD" and "HEAD^".

In short, I can review the latest commit and change the code in one step.

Done

After review and code change, the remaining book-keeping things (git-commit/git-merge/git-push) are easy.

Every operation in previous steps is optimized with some shortcut. For example, in shell I use alias "g" instead of full command line "git status".

Please enlighten me if the work flow could be improved.

How to do the file navigation efficiently

My way is possibily not the best in the world. But I'm sure it's close to world class level because I didn't figure out the solution by myself. People who are much better than me provided the design. I'm just the guy who did the implementation.

Mendel Cooper provided the original idea in his famous Advanced Bash-Scripting Guide. Masafumi Oyamada (AKA mooz) added the missing piece by creating percol.

Problem

You need find full path of a file in a huge project.

The information you've got about the file is in-complete:

  • could be a relative path like "../part/of/path/file-name.txt"
  • could be a file name without file extension like "helloword"

When you got the full path of that file, you want to share it to other applications easily.

The whole work flow should be intuitive and efficient.

Installation

Step 1, Install percol Download the package and extract it. Place the sub-directory named "percol/" into the directory "~/bin". Rename the program "percol" in sub-directory "bin" into "percol.py". Put the percol.py also into "~/bin".

Step 2, Insert below code into ~/.bashrc:

[ $(uname -s | grep -c CYGWIN) -eq 1 ] && OS_NAME="CYGWIN" || OS_NAME=`uname -s`
function pclip() {
    if [ $OS_NAME == CYGWIN ]; then
        putclip $@;
    elif [ $OS_NAME == Darwin ]; then
        pbcopy $@;
    else
        if [ -x /usr/bin/xsel ]; then
            xsel -ib $@;
        else
            if [ -x /usr/bin/xclip ]; then
                xclip -selection c $@;
            else
                echo "Neither xsel or xclip is installed!"
            fi
        fi
    fi
}

function ff()
{
    local fullpath=$*
    local filename=${fullpath##*/} # remove "/" from the beginning
    filename=${filename##*./} # remove  ".../" from the beginning
    echo file=$filename
    #  only the filename without path is needed
    # filename should be reasonable
    local cli=`find $PWD -not -iwholename '*/target/*' -not -iwholename '*.svn*' -not -iwholename '*.git*' -not -iwholename '*.sass-cache*' -not -iwholename '*.hg*' -type f -iwholename '*'${filename}'*' -print | ~/bin/percol.py`
    echo ${cli}
    echo -n ${cli} |pclip;
}

Usage

Type "ff partials-of-file-path" in the bash shell. A filter window will popup. You can filter and scroll down/up in that window to select one file. The full path of the file will be copied into system clipboard automatically (under Linux, you need install either xsel or xclip to access clipboard).

The paritls-of-file-path could contain wildcard character. For example, "…/grunt-docs/*bootstrap*css" is a fine example.

Here is the screen shot when I type "ff el" in my ~/.emacs.d: use-ff-in-shell.png

You will notice that I input the string "inflect" to filter the result.

I can scroll up/down (press Ctrl-P or Ctrl-N) to select the exact file.

BTW, in Emacs community, many people tried to embed a file explorer into Emacs (Sr speedbar, for example). This solution may make embedded file explorer unnecessary. A Sublime guy once asked me to share this trick to him. I guess it's because Sublime's own file explorer is not good enough for him.

Advanced usage

What I have show you is only a prototype. I use it to demostrate the key idea about combining the power of bash/percol/clipboard.

What I actually use in real world is more advanced.

For example, here is the screen shot when I search the bash history. use-h-in-shell.png

Code to insert ~/.bashrc:

function h () {
    # reverse history, pick up one line, remove new line characters and put it into clipboard
    if [ -z "$1" ]; then
        history | sed '1!G;h;$!d' | ~/bin/percol.py | sed -n 's/^ *[0-9][0-9]* *\(.*\)$/\1/p'| tr -d '\n' | pclip
    else
        history | grep "$1" | sed '1!G;h;$!d' | ~/bin/percol.py | sed -n 's/^ *[0-9][0-9]* *\(.*\)$/\1/p'| tr -d '\n' | pclip
    fi
}

Another screen shot I select a file in my git commit. use-glsf-in-shell.png

Code:

function glsf () {
    local str=`git --no-pager log --oneline --stat $* |  ~/bin/percol.py`
    if [[ $str =~ ^[[:space:]]*([a-z0-9A-Z_.\/-]*).*$ ]]; then
        echo -n ${BASH_REMATCH[1]} |pclip;
        echo ${BASH_REMATCH[1]}
    fi
}

Install multiple versions of Emacs into $HOME directory from source

Here is the script,

#!/bin/bash
[ -z "$EMACS_VERSION" ] && EMACS_VERSION="23.4"
[ -z "$EMACS_URL" ] && EMACS_URL="http://gnu.mirror.uber.com.au/emacs"
# I've assign 12G memory to /tmp as ramdisk
[ -z "$EMACS_TMP" ] && EMACS_TMP="/tmp"

curl $EMACS_URL/emacs-$EMACS_VERSION.tar.gz | tar xvz -C $EMACS_TMP
cd $EMACS_TMP/emacs-$EMACS_VERSION;mkdir -p $HOME/myemacs/$EMACS_VERSION;rm -rf $HOME/myemacs/$EMACS_VERSION/*;./configure --prefix=$HOME/myemacs/$EMACS_VERSION --without-xpm --without-png --without-gif --without-tiff --without-jpeg --without-rsvg --without-xft --without-xaw3d --without-xim --without-xpm --without-dbus --without-gconf --without-makeinfo --with-x-toolkit=no --without-sound --without-sync-input --without-pop;make;make install

# clean the installation directory
 rm -rf $EMACS_TMP/emacs-$EMACS_VERSION

Usage:

./install-emacs
#or
EMACS_VERSION=23.4 ./install-emacs

The emacs will be installed into the directory $HOME/myemacs/$EMACS_VERSION

How to manage the file path in big project

It's not good practice to use relative path in big project.

Reasons:

  • If file B refer to file A with "../A". Then B's position in the project *CANNOT be changed. Or else its reference to A will be wrong.
  • Relative path is not intuitive when debugging.
  • If the dependency is complex. Figure out the right path is mission impossible. For example, file A refer to file B with "./a/../../B" and file B refer to file C "../../b/C".

So you should ALWAYS use the absolute path.

Absolute path is tedious to type and not portable.

It could be improved a little bit be use an environment variable to replace the common prefix of the full path.

For example, we can replace absolute path "/home/cb/projects/app1/src/test.c" with "$TTAGROOT/src/test.c", if the value of environment variable TTAGROOT is "/home/cb/projects/app1".

Insert below code into ~/.bashrc so TTAGROOT value is set when you logged into bash:

export TTAGROOT="/home/cb/projects/app1"

In you script to do the real job, you could make TTAGROOT optional and still use your full path happily. It's just one bash liner.

Here is a sample file named test.sh:

#!/bin/bash
[ -z "$TTAGROOT" ] && TTAGROOT="hard-coded-full-path"
echo "TTAGROOT = $TTAGROOT"

You could use test.sh without $TTAGROOT. Or you can set up default value of $TTAGROOT in ~/.bashrc as I already mentioned.

Or you can override the TTAGROOT value when you executing "test.sh":

TTAGROOT="hello" ./test.sh

BTW, don't abuse this technique. Set one environment variable for the root directory of project is enough.

C/C++/Java code indentation in Emacs

Problem

There are two styles when insert curly braces in C like languages.

Style 1:

if(true) {
    printf("hello world\n");
}

Style 2:

if(true)
{
    printf("hello world\n");
}

Whatever style I use, I expect Emacs will properly handle the indentation for me.

In "Style 1", when I press ENTER key after "{" at first line, I expect the new line will indent four spaces.

In "Style 2", when I press ENTER key after ")" at first line, I expect the new line will NOT indent.

Solution

Insert below code into ~/.emacs:

(defun fix-c-indent-offset-according-to-syntax-context (key val)
  ;; remove the old element
  (setq c-offsets-alist (delq (assoc key c-offsets-alist) c-offsets-alist))
  ;; new value
  (add-to-list 'c-offsets-alist '(key . val)))

(add-hook 'c-mode-common-hook
          (lambda ()
            (when (derived-mode-p 'c-mode 'c++-mode 'java-mode)
              ;; indent
              (fix-c-indent-offset-according-to-syntax-context 'substatement 0)
              (fix-c-indent-offset-according-to-syntax-context 'func-decl-cont 0))
            ))

That's it.

Explanation

When you press the ENTER key, the function c-indent-line will be called.

That function will do some simple syntax analysis and decide current syntactic context..

It will use that syntactic context to look up a global variable c-offsets-alist and decide how many spaces the new line will indent.

For example, substatement corresponds to the code like below:

if(true) // press ENTER here

And func-decl-cont corresponds to:

void fn () //press ENTER here

Technical details

When you press ENTER key, the new line will be inserted. Then the function indent-according-to-mode will always be called

indent-according-to-mode will actually call function object indent-line-function if it's not nil.

In C/C++/Java, that object is actually c-indent-line.

c-indent-line is defined in /usr/share/emacs/24.3/lisp/progmodes/cc-cmds.el (I use Emacs 24.3 on Gentoo Linux).

In that function, just below the code line:

(setq c-syntactic-context (c-guess-basic-syntax))

Please insert insert log code:

(message "c-syntactic-context=%s" c-syntactic-context)

Then you will know the current syntactic context when you press ENTER key.

EmacsWiki said you can run command "c-set-offset", whose hot key is "C-x C-o", in order to "see the syntax at point". As I tested, it does not work as expected. My way may seem a little bit intrusive but is reliable.

For example, the context statement-cont corresponds to the use case like this:

int a=3, // press ENTER here

Please note syntax analysis in c-indent-line is turned on if and only if the global flag c-syntactic-indentation is true.

Thanks for chengyi for reporting the issue and suggesting the fix.

BTW, EmacsWiki has a section to discuss the indenting in C. You may not need it if you have read this article and can read the Emacs lisp code.

Why Gnus is better than Gmail

Here is my use case. My agent notify me that there is a potential contract from a company named "FF".

My first reaction is to reply the email with "Great! Please forward me CV".

Before I press the "Send" button, it occurs to me that other agents have possibily already submitted my CV to FF since it is a big organization. I need double check.

I save current email as draft, search all the mails containing "FF" and forward them to the original email I've not sent yet. Then my agent could figure out whether other guys have already represented me for the same opportunity.

This operation is doable in desktop application like Outlook. I need search emails in a new dialog box. Select emails. Then drag them to the original email.

It's hard to do this kind of thing in Gmail.

Now let me show you how Emacs get the job done:

  • Step 1, Switch to Groups buffer (the buffer which lists email folder). press key "G G" or run command "M-x gnus-group-make-nnir-group", input the keyword "FF" to start search
  • Step 2, Mark the emails I want to forward with hot key "#"
  • Step 3, Press key "C-c C-f" or run command "M-x gnus-summary-mail-forward". A new buffer is created. It contains a big chuck of xml string wrapped by either "<#multipart>" tag or "<#mml>" tag.
  • Step 4, Select and copy that string into you original email. Done!

Well, some people may argue that step 4 could be improved a little bit.

In Emacs, everyting could be optimized. Please copy below code into your ~/.emacs:

(defun message-select-forwarded-email-tags ()
  "select the <#mml-or-what-ever> tags in message-mode"
  (interactive)
  (let (start rlt)
    (when (search-forward "<#")
      (setq start (point))
      (push-mark (point) t t)
      (goto-char (point-max))
      (search-backward ">")
      (forward-char)
      (setq rlt t))
    rlt))

(defun message-copy-select-forwarded-email-tags ()
  "copy the <#mml-or-what-ever> tags in message-mode"
  (interactive)
  (save-excursion
    (cond
     ((message-select-forwarded-email-tags)
      (copy-region-as-kill (region-beginning) (region-end))
      (message "forwarded email tags copied!"))
     (t (message "NO forwarded email tags found!"))
     )
    ))

All you need is "M-x message-copy-select-forwarded-email-tags" to copy the tags into kill-ring.

UPDATE: This is only a case study. My complete guide on Gnus is at http://blog.binchen.org/posts/notes-on-using-gnus.html.

What's the best spell check set up in emacs

To save your time, I started from conclusion.

Suggestion for non-programmers

Emacs will find the right dictionary by querying your locale.

Run command "locale" in your shell to get current locale.

If you want to force Emacs use dictionary "en_US", copy below code into your ~/.emacs:

;; find aspell and hunspell automatically
(cond
 ((executable-find "aspell")
  (setq ispell-program-name "aspell")
  (setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US")))
 ((executable-find "hunspell")
  (setq ispell-program-name "hunspell")
  (setq ispell-extra-args '("-d en_US")))
 )

That's it!

Please run command "man aspell" or "man hunspell" in shell if you have more questions. I've nothing more to say.

Suggestion for programmers

I strongly recommend aspell instead of hunspell (Though hunspell is fine).

Please insert below code into your ~/.emacs:

;; if (aspell installed) { use aspell}
;; else if (hunspell installed) { use hunspell }
;; whatever spell checker I use, I always use English dictionary
;; I prefer use aspell because:
;; 1. aspell is older
;; 2. looks Kevin Atkinson still get some road map for aspell:
;; @see http://lists.gnu.org/archive/html/aspell-announce/2011-09/msg00000.html
(defun flyspell-detect-ispell-args (&optional RUN-TOGETHER)
  "if RUN-TOGETHER is true, spell check the CamelCase words"
  (let (args)
    (cond
     ((string-match  "aspell$" ispell-program-name)
      ;; force the English dictionary, support Camel Case spelling check (tested with aspell 0.6)
      (setq args (list "--sug-mode=ultra" "--lang=en_US"))
      (if RUN-TOGETHER
          (setq args (append args '("--run-together" "--run-together-limit=5" "--run-together-min=2")))))
     ((string-match "hunspell$" ispell-program-name)
      (setq args nil)))
    args
    ))

(cond
 ((executable-find "aspell")
  (setq ispell-program-name "aspell"))
 ((executable-find "hunspell")
  (setq ispell-program-name "hunspell")
  ;; just reset dictionary to the safe one "en_US" for hunspell.
  ;; if we need use different dictionary, we specify it in command line arguments
  (setq ispell-local-dictionary "en_US")
  (setq ispell-local-dictionary-alist
        '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil nil nil utf-8))))
 (t (setq ispell-program-name nil)))

;; ispell-cmd-args is useless, it's the list of *extra* arguments we will append to the ispell process when "ispell-word" is called.
;; ispell-extra-args is the command arguments which will *always* be used when start ispell process
(setq ispell-extra-args (flyspell-detect-ispell-args t))
;; (setq ispell-cmd-args (flyspell-detect-ispell-args))
(defadvice ispell-word (around my-ispell-word activate)
  (let ((old-ispell-extra-args ispell-extra-args))
    (ispell-kill-ispell t)
    (setq ispell-extra-args (flyspell-detect-ispell-args))
    ad-do-it
    (setq ispell-extra-args old-ispell-extra-args)
    (ispell-kill-ispell t)
    ))

That's it.

Why

  • Aspell
    apell is recommended because its option "–run-together". That option could spell check the camel case word. Variable name often uses camel case naming convention these days. Read my Effective spell check in Emacs for advanced tips on how to use flyspell to check variable names.

    If Emacs start a aspell process with "–run-together" option, that process is not closed so it can be re-used by other commands.

    This behaviour will be a problem if you want to let Emacs/aspell correct the typo by running the command "ispell-word" because a apell process with "–run-together" will produce much noise.

    For example, for a typo "helle" Emacs will give you too many candidates. It's hard to find the desired word "hello": aspell-camelcase-suggest-nq8.png

    The better solution is before running "M-x ispell-word", we'd better restart a aspell proces without the argument "–run-together".

    Here is the screen shot after we applying this fix: aspell-normal-suggest-nq8.png

    As I mentioned, the global variable "ispell-extra-args" contains arguments Emacs will always append to a spell checker process (aspell or hunspell). That's the only variable you need care about.

    There is another variable named "ispell-cmd-args". It is actually some extra arguments Emacs could send to an existing spell checker process when you "M-x ispell-word". In my opinion, it's useless. I mention it because the naming are really confusing. "ispell-extra-args" is actually command line arguments the spell checker will always use. The "ispell-cmd-args" are actually the extra arguments will be used in certain cases.

  • Hunspell
    I cannot find hunspell option to check camel case words. Please enlighten me if you know the option.

    Hunspell has some design flaw. It will always check the environment variable LC_ALL, LC_MESSAGES and LANG at first to find the default dictionary unless you specify the dictionary in the command line. If it cannot find the default dictionary, the spell checker process won't start. Aspell does not have this issue, if it cannot find the zh_CN dictionary, it will fall back into English.

    Specify the ispell-extra-args won't stop hunspell to search for the default dictionary at the beginning.

    For example, I am a Chinese and my locale is "zh_CN.utf-8". So hunspell will always search the dictionary zh_CN. Even I'm only interested in English spell checking.

    To specify the dictionary explicitly, I need hack the Emacs code which is kind of mess. Finally, I figure out. It's just several lines of elisp code:

    (setq ispell-program-name "hunspell")
    (setq ispell-local-dictionary "en_US")
    (setq ispell-local-dictionary-alist
          '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil nil nil utf-8)))
    

How to embed button in wxDataViewListCtrl

Before creating wxDataViewListCtrl, it's better to detect information about text size so that we can set the column width and height intelligently.

Here is the code:

void GuessRowSize(int* w, int* h) {
    //@see http://sourceforge.net/apps/trac/codelite/browser/trunk/LiteEditor/new_build_tab.cpp?rev=5804

// Determine the row height
    wxBitmap tmpBmp(1, 1);
    wxMemoryDC memDc;
    memDc.SelectObject(tmpBmp);
    wxFont f = wxSystemSettings::GetFont(wxSYS_ANSI_FIXED_FONT);
    int xx, yy;
    memDc.GetTextExtent(wxT("Tp"), &xx, &yy, NULL, NULL, &f);

    //enough height for ICON
    *h=yy<16? 16: yy;

    memDc.GetTextExtent("Wp", &xx, &yy, NULL, NULL, &f);
    *w=xx/2;

    return;
}
wxDataViewListCtrl* ctrl=new wxDataViewListCtrl(parent,-1);
ctrl->Create(parent,id,wxDefaultPosition,wxDefaultSize);
int w,h;
GuessRowSize(&w,&h);
//hard code width may not be good, may be can use w ,h
AppendTextColumn(_T("Column 1"),wxDATAVIEW_CELL_INERT,200 /*width*/);
//must be called after Create()
ctrl->SetRowHeight(h+6);


// well I need provide button render by myself
class wxDataViewMyButtonRenderer: public wxDataViewCustomRenderer, public wxTimer
{
public:
    wxDataViewMyButtonRenderer( const wxString &varianttype = wxT("wxString"),
                              int align = wxDVR_DEFAULT_ALIGNMENT );

    virtual bool SetValue( const wxVariant &value );
    virtual bool GetValue( wxVariant &value ) const;

    virtual bool Render( wxRect, wxDC*, int);
    virtual wxSize GetSize() const;
    virtual void Notify();
    void SetParent(GalleryListView*);
    // Implementation only, don't use nor override
    virtual bool ActivateCell(const wxRect& rect,
        wxDataViewModel *model,
        const wxDataViewItem& item,
        unsigned int col,
        const wxMouseEvent *mouseEvent);

private:
    wxString m_value;
    bool m_button_clicked;
    wxRect m_cell_rect;
    GalleryListView* m_parent;

protected:
    DECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewMyButtonRenderer)
};

When user click one row, the ActivateCell is called, we can use some rect detect algorithm to find if the button rect is clicked.

There is no mouse up event handler! So I have to hack, basically start a wxTimer when button clicked and draw the button up effect after about 1 second in Notify(). That's why I ask wxDataViewMyButtonRenderer to inherit from wxTimer.

To draw the button up effect, I need ask the parent wxDataViewListCtrl to refresh itself, so that the button's Render() method has a chance to be called. That's why we need SetParent().

Here is the part of implementation:

bool
wxDataViewMyButtonRenderer::ActivateCell(const wxRect& rect,
    wxDataViewModel *model,
    const wxDataViewItem& item,
    unsigned int col,
    const wxMouseEvent *mouseEvent)
{
    wxDataViewListStore* store=(wxDataViewListStore*) model;

    if ( mouseEvent ) {
        if ( !wxRect(GetSize()).Contains(mouseEvent->GetPosition()) ){
            return false;
        }
        wxVariant item_value_pdf;
        store->GetValueByRow(item_value_pdf , store->GetRow(item),0);
        wxVariant item_value_created;
        store->GetValueByRow(item_value_created , store->GetRow(item),1);

        m_button_clicked=true;
        m_cell_rect=rect;
    } else {
        wxLogDebug(_T("Sorry, I don't handle keyboard"));
    }

    return true;
}

bool
wxDataViewMyButtonRenderer::Render( wxRect rect, wxDC *dc, int state )
{
    wxLogDebug(_T("Render called"));
    // Ensure that the check boxes always have at least the minimal required
    // size, otherwise DrawCheckBox() doesn't really work well. If this size is
    // greater than the rect size, the checkbox will be truncated but this is a
    // lesser evil.
    wxSize size = rect.GetSize();
    size.IncTo(GetSize());
    rect.SetSize(size);

    // draw button
    if(m_button_clicked==true && rect.Intersects(m_cell_rect)){
        //draw the button when clicked
        dc->SetBrush(wxBrush(wxColour(65, 150, 65), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );
        dc->DrawRectangle(rect);

        // draw button push down effect. since we cannot detect mouse up event, we use
        // timer to draw it
        const int time_to_button_up=350;
        StartOnce(time_to_button_up);
    } else {
        // draw normal button
        dc->SetBrush(wxBrush(wxColour(84, 174, 84), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );
        dc->DrawRectangle(rect);

        //draw inner white border
        rect.Deflate(1);
        dc->SetPen( *wxWHITE_PEN );
        dc->DrawRectangle(rect);
    }

    dc->SetTextForeground(*wxWHITE);
    dc->DrawLabel(m_value,wxRect(dc->GetTextExtent(m_value)).CentreIn(rect));

    return true;
}
Contents © 2014 Chen Bin - Powered by Nikola