File indexing completed on 2024-04-21 05:42:25

0001 /*
0002   SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved.
0003   SPDX-FileCopyrightText:  2018-2020 Michael Reeves reeves.87@gmail.com
0004   SPDX-License-Identifier: BSD-2-Clause
0005 */
0006 
0007 #include "diff_ext.h"
0008 
0009 #include <KLocalizedString>
0010 
0011 #include <QString>
0012 
0013 #include <assert.h>
0014 #include <stdio.h>
0015 #include <tchar.h>
0016 
0017 #include <map>
0018 #include <vector>
0019 
0020 DIFF_EXT::DIFF_EXT():
0021     m_nrOfSelectedFiles(0), _ref_count(0L),
0022     m_recentFiles(SERVER::instance()->recent_files())
0023 {
0024     LOG();
0025     _resource = SERVER::instance()->handle();
0026 
0027     SERVER::instance()->lock();
0028 }
0029 
0030 DIFF_EXT::~DIFF_EXT()
0031 {
0032     LOG();
0033     if(_resource != SERVER::instance()->handle())
0034     {
0035         FreeLibrary(_resource);
0036     }
0037 
0038     SERVER::instance()->release();
0039 }
0040 
0041 STDMETHODIMP
0042 DIFF_EXT::QueryInterface(REFIID refiid, void** ppv)
0043 {
0044     HRESULT ret = E_NOINTERFACE;
0045     *ppv = 0;
0046 
0047     if(IsEqualIID(refiid, IID_IShellExtInit) || IsEqualIID(refiid, IID_IUnknown))
0048     {
0049         *ppv = static_cast<IShellExtInit*>(this);
0050     }
0051     else if(IsEqualIID(refiid, IID_IContextMenu))
0052     {
0053         *ppv = static_cast<IContextMenu*>(this);
0054     }
0055 
0056     if(*ppv != 0)
0057     {
0058         AddRef();
0059 
0060         ret = NOERROR;
0061     }
0062 
0063     return ret;
0064 }
0065 
0066 STDMETHODIMP_(ULONG)
0067 DIFF_EXT::AddRef()
0068 {
0069     return InterlockedIncrement((LPLONG)&_ref_count);
0070 }
0071 
0072 STDMETHODIMP_(ULONG)
0073 DIFF_EXT::Release()
0074 {
0075     ULONG ret = 0L;
0076 
0077     if(InterlockedDecrement((LPLONG)&_ref_count) != 0)
0078     {
0079         ret = _ref_count;
0080     }
0081     else
0082     {
0083         delete this;
0084     }
0085 
0086     return ret;
0087 }
0088 
0089 STDMETHODIMP
0090 DIFF_EXT::Initialize(LPCITEMIDLIST /*folder not used*/, IDataObject* data, HKEY /*key not used*/)
0091 {
0092     LOG();
0093 
0094     FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
0095     STGMEDIUM medium;
0096     medium.tymed = TYMED_HGLOBAL;
0097     HRESULT ret = E_INVALIDARG;
0098 
0099     if(data->GetData(&format, &medium) == S_OK)
0100     {
0101         HDROP drop = (HDROP)medium.hGlobal;
0102         m_nrOfSelectedFiles = DragQueryFile(drop, 0xFFFFFFFF, 0, 0);
0103 
0104         TCHAR tmp[MAX_PATH];
0105 
0106         if(m_nrOfSelectedFiles >= 1 && m_nrOfSelectedFiles <= 3)
0107         {
0108             DragQueryFile(drop, 0, tmp, MAX_PATH);
0109             _file_name1 = tmp;
0110 
0111             if(m_nrOfSelectedFiles >= 2)
0112             {
0113                 DragQueryFile(drop, 1, tmp, MAX_PATH);
0114                 _file_name2 = tmp;
0115             }
0116 
0117             if(m_nrOfSelectedFiles == 3)
0118             {
0119                 DragQueryFile(drop, 2, tmp, MAX_PATH);
0120                 _file_name3 = tmp;
0121             }
0122 
0123             ret = S_OK;
0124         }
0125     }
0126     else
0127     {
0128         SYSERRORLOG(TEXT("GetData"));
0129     }
0130 
0131     return ret;
0132 }
0133 
0134 static int insertMenuItemHelper(HMENU menu, UINT id, UINT position, const tstring& text,
0135                                 UINT fState = MFS_ENABLED, HMENU hSubMenu = 0)
0136 {
0137     MENUITEMINFO item_info;
0138     ZeroMemory(&item_info, sizeof(item_info));
0139     item_info.cbSize = sizeof(MENUITEMINFO);
0140     item_info.wID = id;
0141     if(text.empty())
0142     { // Separator
0143         item_info.fMask = MIIM_TYPE;
0144         item_info.fType = MFT_SEPARATOR;
0145         item_info.dwTypeData = 0;
0146     }
0147     else
0148     {
0149         item_info.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | (hSubMenu != 0 ? MIIM_SUBMENU : 0);
0150         item_info.fType = MFT_STRING;
0151         item_info.fState = fState;
0152         item_info.dwTypeData = (LPTSTR)text.c_str();
0153         item_info.hSubMenu = hSubMenu;
0154     }
0155     if(0 == InsertMenuItem(menu, position, TRUE, &item_info))
0156         SYSERRORLOG(TEXT("InsertMenuItem"));
0157     return id;
0158 }
0159 
0160 STDMETHODIMP
0161 DIFF_EXT::QueryContextMenu(HMENU menu, UINT position, UINT first_cmd, UINT /*last_cmd not used*/, UINT flags)
0162 {
0163     LOG();
0164 
0165     SERVER::instance()->recent_files(); // updates recent files list (reads from registry)
0166 
0167     m_id_Diff = UINT(-1);
0168     m_id_DiffWith = UINT(-1);
0169     m_id_DiffLater = UINT(-1);
0170     m_id_MergeWith = UINT(-1);
0171     m_id_Merge3 = UINT(-1);
0172     m_id_Diff3 = UINT(-1);
0173     m_id_DiffWith_Base = UINT(-1);
0174     m_id_ClearList = UINT(-1);
0175     m_id_About = UINT(-1);
0176 
0177     HRESULT ret = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
0178 
0179     if(!(flags & CMF_DEFAULTONLY))
0180     {
0181         /* Menu structure:
0182            KDiff3 -> (1 File selected):  Save 'selection' for later comparison (push onto history stack)
0183                                          Compare 'selection' with first file on history stack.
0184                                          Compare 'selection' with -> choice from history stack
0185                                          Merge 'selection' with first file on history stack.
0186                                          Merge 'selection' with last two files on history stack.
0187                      (2 Files selected): Compare 's1' with 's2'
0188                                          Merge 's1' with 's2'
0189                      (3 Files selected): Compare 's1', 's2' and 's3'
0190         */
0191         HMENU subMenu = CreateMenu();
0192 
0193         UINT id = first_cmd;
0194         m_id_FirstCmd = first_cmd;
0195 
0196         insertMenuItemHelper(menu, id++, position++, TEXT("")); // begin separator
0197 
0198         tstring menuString;
0199         UINT pos2 = 0;
0200         if(m_nrOfSelectedFiles == 1)
0201         {
0202             size_t nrOfRecentFiles = m_recentFiles.size();
0203             tstring menuStringCompare;
0204             tstring menuStringMerge;
0205             tstring firstFileName;
0206             if(nrOfRecentFiles >= 1)
0207             {
0208                 firstFileName = TEXT("'") + cut_to_length(m_recentFiles.front()) + TEXT("'");
0209             }
0210 
0211             menuStringCompare = fromQString(i18nc("Contexualmenu option", "Compare with %1", toQString(firstFileName)));
0212             menuStringMerge = fromQString(i18nc("Contexualmenu option", "Merge with %1", toQString(firstFileName)));
0213 
0214             m_id_DiffWith = insertMenuItemHelper(subMenu, id++, pos2++, menuStringCompare, nrOfRecentFiles >= 1 ? MFS_ENABLED : MFS_DISABLED);
0215             m_id_MergeWith = insertMenuItemHelper(subMenu, id++, pos2++, menuStringMerge, nrOfRecentFiles >= 1 ? MFS_ENABLED : MFS_DISABLED);
0216 
0217             m_id_Merge3 = insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "3-way merge with base")),
0218                                                nrOfRecentFiles >= 2 ? MFS_ENABLED : MFS_DISABLED);
0219 
0220             menuString = fromQString(i18nc("Contexualmenu option", "Save '%1' for later", toQString(_file_name1)));
0221             m_id_DiffLater = insertMenuItemHelper(subMenu, id++, pos2++, menuString);
0222 
0223             HMENU file_list = CreateMenu();
0224             std::list<tstring>::iterator i;
0225             m_id_DiffWith_Base = id;
0226             int n = 0;
0227             for(i = m_recentFiles.begin(); i != m_recentFiles.end(); ++i)
0228             {
0229                 tstring s = cut_to_length(*i);
0230                 insertMenuItemHelper(file_list, id++, n, s);
0231                 ++n;
0232             }
0233 
0234             insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "Compare with ...")),
0235                                  nrOfRecentFiles > 0 ? MFS_ENABLED : MFS_DISABLED, file_list);
0236 
0237             m_id_ClearList = insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "Clear list")), nrOfRecentFiles >= 1 ? MFS_ENABLED : MFS_DISABLED);
0238         }
0239         else if(m_nrOfSelectedFiles == 2)
0240         {
0241             //= "Diff " + cut_to_length(_file_name1, 20)+" and "+cut_to_length(_file_name2, 20);
0242             m_id_Diff = insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "Compare")));
0243         }
0244         else if(m_nrOfSelectedFiles == 3)
0245         {
0246             m_id_Diff3 = insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "3 way comparison")));
0247         }
0248         else
0249         {
0250             // More than 3 files selected?
0251         }
0252         m_id_About = insertMenuItemHelper(subMenu, id++, pos2++, fromQString(i18nc("Contexualmenu option", "About Diff-Ext ...")));
0253 
0254         insertMenuItemHelper(menu, id++, position++, TEXT("KDiff3"), MFS_ENABLED, subMenu);
0255 
0256         insertMenuItemHelper(menu, id++, position++, TEXT("")); // final separator
0257 
0258         ret = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, id - first_cmd);
0259     }
0260 
0261     return ret;
0262 }
0263 
0264 STDMETHODIMP
0265 DIFF_EXT::InvokeCommand(LPCMINVOKECOMMANDINFO ici)
0266 {
0267     HRESULT ret = NOERROR;
0268 
0269     _hwnd = ici->hwnd;
0270 
0271     if(HIWORD(ici->lpVerb) == 0)
0272     {
0273         UINT id = m_id_FirstCmd + LOWORD(ici->lpVerb);
0274         if(id == m_id_Diff)
0275         {
0276             LOG();
0277             diff(TEXT("\"") + _file_name1 + TEXT("\" \"") + _file_name2 + TEXT("\""));
0278         }
0279         else if(id == m_id_Diff3)
0280         {
0281             LOG();
0282             diff(TEXT("\"") + _file_name1 + TEXT("\" \"") + _file_name2 + TEXT("\" \"") + _file_name3 + TEXT("\""));
0283         }
0284         else if(id == m_id_Merge3)
0285         {
0286             LOG();
0287             std::list<tstring>::iterator iFrom = m_recentFiles.begin();
0288             std::list<tstring>::iterator iBase = iFrom;
0289             ++iBase;
0290             diff(TEXT("-m \"") + *iBase + TEXT("\" \"") + *iFrom + TEXT("\" \"") + _file_name1 + TEXT("\""));
0291         }
0292         else if(id == m_id_DiffWith)
0293         {
0294             LOG();
0295             diff_with(0, false);
0296         }
0297         else if(id == m_id_MergeWith)
0298         {
0299             LOG();
0300             diff_with(0, true);
0301         }
0302         else if(id == m_id_ClearList)
0303         {
0304             LOG();
0305             m_recentFiles.clear();
0306             SERVER::instance()->save_history();
0307         }
0308         else if(id == m_id_DiffLater)
0309         {
0310             MESSAGELOG(TEXT("Diff Later: ") + _file_name1);
0311             m_recentFiles.remove(_file_name1);
0312             m_recentFiles.push_front(_file_name1);
0313             SERVER::instance()->save_history();
0314         }
0315         else if(id >= m_id_DiffWith_Base && id < m_id_DiffWith_Base + m_recentFiles.size())
0316         {
0317             LOG();
0318             diff_with(id - m_id_DiffWith_Base, false);
0319         }
0320         else if(id == m_id_About)
0321         {
0322             LOG();
0323 
0324             MessageBox(_hwnd, (fromQString(i18n("Diff-Ext Copyright (c) 2003-2006, Sergey Zorin. All rights reserved.\n") + i18n("This software is distributable under the BSD-2-Clause license.\n") + i18n("Some extensions for KDiff3 (c) 2006-2013 by Joachim Eibl.\n") + i18n("Ported to Qt5/Kf5 by Michael Reeves\n") + i18n("Homepage for Diff-Ext: http://diff-ext.sourceforge.net\n"))).c_str(), fromQString(i18n("About Diff-Ext for KDiff3 (64 Bit)")).c_str(), MB_OK);
0325         }
0326         else
0327         {
0328             ret = E_INVALIDARG;
0329             TCHAR verb[80];
0330             _sntprintf(verb, 79, TEXT("Command id: %d"), LOWORD(ici->lpVerb));
0331             verb[79] = 0;
0332             ERRORLOG(verb);
0333         }
0334     }
0335     else
0336     {
0337         ret = E_INVALIDARG;
0338     }
0339 
0340     return ret;
0341 }
0342 
0343 STDMETHODIMP
0344 DIFF_EXT::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT*, LPSTR pszName, UINT cchMax)
0345 {
0346     HRESULT ret = NOERROR;
0347 
0348     if(uFlags == GCS_HELPTEXT)
0349     {
0350         QString helpString;
0351         if(idCmd == m_id_Diff)
0352         {
0353             helpString = i18nc("Contexualmenu option", "Compare selected files");
0354         }
0355         else if(idCmd == m_id_DiffWith)
0356         {
0357             if(!m_recentFiles.empty())
0358             {
0359                 helpString = i18nc("Contexualmenu option", "Compare '%1' with '%2'", toQString(_file_name1), toQString(m_recentFiles.front()));
0360             }
0361         }
0362         else if(idCmd == m_id_DiffLater)
0363         {
0364             helpString = i18nc("Contexualmenu option", "Save '%1' for later operation", toQString(_file_name1));
0365         }
0366         else if((idCmd >= m_id_DiffWith_Base) && (idCmd < m_id_DiffWith_Base + m_recentFiles.size()))
0367         {
0368             if(!m_recentFiles.empty())
0369             {
0370                 unsigned long long num = idCmd - m_id_DiffWith_Base;
0371                 std::list<tstring>::iterator i = m_recentFiles.begin();
0372                 for(unsigned long long j = 0; j < num && i != m_recentFiles.end(); j++)
0373                     i++;
0374 
0375                 if(i != m_recentFiles.end())
0376                 {
0377                     helpString = i18nc("Contexualmenu option", "Compare '%1' with '%2'", toQString(_file_name1), toQString(*i));
0378                 }
0379             }
0380         }
0381         lstrcpyn((LPTSTR)pszName, fromQString(helpString).c_str(), cchMax);
0382     }
0383     else
0384     {
0385         ret = E_INVALIDARG;
0386     }
0387 
0388     return ret;
0389 }
0390 
0391 void DIFF_EXT::diff(const tstring& arguments)
0392 {
0393     LOG();
0394     STARTUPINFO si;
0395     PROCESS_INFORMATION pi;
0396     bool bError = true;
0397     tstring command = SERVER::instance()->getRegistryKeyString(TEXT(""), TEXT("diffcommand"));
0398     tstring commandLine = TEXT("\"") + command + TEXT("\" ") + arguments;
0399     if(!command.empty())
0400     {
0401         ZeroMemory(&si, sizeof(si));
0402         si.cb = sizeof(si);
0403         if(CreateProcess(command.c_str(), (LPTSTR)commandLine.c_str(), 0, 0, FALSE, 0, 0, 0, &si, &pi) == 0)
0404         {
0405             SYSERRORLOG(TEXT("CreateProcess") + command);
0406         }
0407         else
0408         {
0409             bError = false;
0410             CloseHandle(pi.hProcess);
0411             CloseHandle(pi.hThread);
0412         }
0413     }
0414 
0415     if(bError)
0416     {
0417         tstring message = fromQString(i18n("Could not start KDiff3. Please rerun KDiff3 installation."));
0418         message += TEXT("\n") + fromQString(i18n("Command")) + TEXT(": ") + command;
0419         message += TEXT("\n") + fromQString(i18n("CommandLine")) + TEXT(": ") + commandLine;
0420         MessageBox(_hwnd, message.c_str(), fromQString(i18n("Diff-Ext For KDiff3")).c_str(), MB_OK);
0421     }
0422 }
0423 
0424 void DIFF_EXT::diff_with(unsigned int num, bool bMerge)
0425 {
0426     LOG();
0427     std::list<tstring>::iterator i = m_recentFiles.begin();
0428     for(unsigned int j = 0; j < num && i != m_recentFiles.end(); j++)
0429     {
0430         i++;
0431     }
0432 
0433     if(i != m_recentFiles.end())
0434         _file_name2 = *i;
0435 
0436     diff((bMerge ? TEXT("-m \"") : TEXT("\"")) + _file_name2 + TEXT("\" \"") + _file_name1 + TEXT("\""));
0437 }
0438 
0439 tstring
0440 DIFF_EXT::cut_to_length(const tstring& in, size_t max_len)
0441 {
0442     tstring ret;
0443     if(in.length() > max_len)
0444     {
0445         ret = in.substr(0, (max_len - 3) / 2);
0446         ret += TEXT("...");
0447         ret += in.substr(in.length() - (max_len - 3) / 2);
0448     }
0449     else
0450     {
0451         ret = in;
0452     }
0453 
0454     return ret;
0455 }