Patent z JButton.setEnabled(false); zaraz po wejściu do procedury obsługi jest poprawny. Wyłączenie źródła komunikatów na czas wykonywania akcji jest właściwe. To co powinieneś zrobić w procedurze obsługi zaraz potem zależy od tego czy te długotrwałe działania będą używać wyłącznie operacji na obiektach Swinga, czy będą robić inne długotrwałe działania. W pierwszym wypadku wywołujesz po prostu metodę invokeLater() z argumentem będącym najprościej instancją klasy, w której metodzie run zapiszesz te długotrwałe działania. W drugim wypadku albo tworzysz nowy wątek i odpalasz go za pomocą Thread.start(), albo tworzysz obiekt bazujący na SwingWorker<T,V> (również może być anonimowy). W obu tych opcjach metoda Thread.run(), albo SwingWorker<T,V>.doInBackground() ma zapisaną długotrwałą sekwencję działań, która wykona się osobnym wątku niezależnym od wątku obsługi zdarzeń Swinga. Jeżeli wśród tych sekwencji potrzebne będą przełączenia lub zmiany stanu kontrolek, to będziesz musiał również tam użyć metody invokeLater() (wszelkie operacje na GUI Swinga należy wykonywać w wątku obsługi Swinga, a invokeLater to zapewnia). Gdy ta długotrwała sekwencja się zakończy, wtedy powinieneś ponownie wywołać właściwy JButton.setEnable(true). W przypadku użycia Thread powinieneś zrobić to na końcu metody run (oczywiście invokeLater), a w przypadku SwingWorkera możesz to zrobić w przeciążonej przez Ciebie metodzie SwingWorker<T,V>.done().
W przypadku SwingWorkera wiele osób ma problemy z jego użyciem z powodu zakłopotania z typami parametryzowanymi. W Twoim wypadku typem wyniku powinien być Void, a typem wyniku pośredniego albo Void, albo Integer. W tym drugim wypadku (SwingWorker<Void, Integer>) świetnie nadaje się on jako wskaźnik postępu zadania we wszelkich kontrolkach pokazujących postęp zadania (np. w JProgressBar). Przeszukaj sobie nawet to forum z kluczem SwingWorker, to Ci się rozjaśni. I przy okazji nauczysz się bardzo wygodnego mechanizmu.
W przypadku takiego użycia jakie opisałem cała obsługa klawisza (która jest wywołana z wątku Event Dispatch Thread) sprowadza się do jednego szybkiego (bo asynchronicznego) wywołania Thread.start() lub równie szybkiego (i również asynchronicznego) SwingWorker<Void, Integer>.execute().
Co do wciśniętego i zamrożonego klawisza, to działa to w ten sposób: Zdarzenie wywołania akcji (actionPerformed) generuje się po otrzymaniu (typowo) i opracowaniu zdarzenia keyReleased lub MouseReleased, tak więc kiedy wywołuje się actionPerformed JButtona, to jest on w stanie wciśniętym i gotowy do "wyciśnięcia". To ostanie zdarzy się dopiero po powrocie z procedury actionPerformed ( w wątku Event Dispatch Thread), więc dopóki procedura ta trwa button jest wciśnięty i zamrożony. Dlatego kluczowe jest w Swingu aby wszelkie procedury były jak najkrótsze lub asynchroniczne.
Co do showSaveDialog(this), to jest to wywołanie metody int JFileChooser.showSaveDialog(Component parent), która wymaga jako argument po prostu swingowego komponentu rodzica, którym może być dowolne okno najwyższego poziomu lub podokno czy inny komponent AWT. Używałeś this ponieważ robiłeś wszystko pewnie w jednym oknie, którego klasa zawiera zapewne cały kod aplikacji. Możesz użyć dowolnej innej zmiennej, pola lub wyniku metody (typowo get-cośtam) będącej referencją do tego obiektu okna, która u Ciebie przyjmowała szczególną postać this. Możesz ją przekazać do obiektu klasy takiej jak Thread czy SwingWorker tak samo jak każdą inną referencję - przez konstruktor i pole, albo jako wywołanie metody, którą sobie stworzysz.