]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Merge branch 'Applications/19.04'
authorElvis Angelaccio <elvis.angelaccio@kde.org>
Sat, 20 Apr 2019 14:26:55 +0000 (16:26 +0200)
committerElvis Angelaccio <elvis.angelaccio@kde.org>
Sat, 20 Apr 2019 14:26:55 +0000 (16:26 +0200)
25 files changed:
CMakeLists.txt
HACKING.md [new file with mode: 0644]
cmake/FindGem.cmake [new file with mode: 0644]
cmake/FindGem.cmake.in [new file with mode: 0644]
src/CMakeLists.txt
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/private/kbaloorolesprovider.cpp
src/org.kde.dolphin.desktop
src/panels/information/filemetadataconfigurationdialog.h
src/panels/information/informationpanel.cpp
src/panels/information/informationpanelcontent.cpp
src/panels/information/informationpanelcontent.h
src/search/dolphinsearchbox.cpp
src/settings/dolphinsettingsdialog.cpp
src/settings/dolphinsettingsdialog.h
src/settings/services/servicemenudeinstallation
src/settings/services/servicemenuinstallation
src/settings/services/test/service_menu_deinstallation_test.rb [new file with mode: 0644]
src/settings/services/test/service_menu_installation_test.rb [new file with mode: 0644]
src/settings/services/test/test_helper.rb [new file with mode: 0644]
src/settings/services/test/test_run.rb [new file with mode: 0755]
src/tests/CMakeLists.txt
src/tests/kfileitemmodeltest.cpp

index bb444c9b6d6fb0f898588447beec043371610071..474fbc839199b401e1219e4fa707b9062e020d30 100644 (file)
@@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.0)
 
 # KDE Application Version, managed by release script
 set (KDE_APPLICATIONS_VERSION_MAJOR "19")
-set (KDE_APPLICATIONS_VERSION_MINOR "04")
-set (KDE_APPLICATIONS_VERSION_MICRO "0")
+set (KDE_APPLICATIONS_VERSION_MINOR "07")
+set (KDE_APPLICATIONS_VERSION_MICRO "70")
 set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}")
 project(Dolphin VERSION ${KDE_APPLICATIONS_VERSION})
 
diff --git a/HACKING.md b/HACKING.md
new file mode 100644 (file)
index 0000000..34acf95
--- /dev/null
@@ -0,0 +1,33 @@
+Philosophy
+==========
+
+Dolphin is a file manager focusing on usability. When reading the term Usability people often assume that the focus is on newbies and only basic features are offered. This assumption is wrong.
+
+Target User Group
+-----------------
+
+Focusing on usability means that features are discoverable and efficient to use. The feature set is defined indirectly by the target user group of Dolphin:
+
+- **Lisa**: Lisa is familiar with computers since 10 years. Due to her job she has experience with Word, Excel and Outlook. At home she mainly uses the computer for browsing the web and writing e-mails. She requires a file manager for managing photos from the camera, documents she gets per e-mail or PDF-documents she downloads with a browser. Lisa knows concepts like folders and a file hierarchy, but she is not familiar with the file hierarchy of Linux.
+
+- **Simon**: Simon has been a developer at a software company for 8 years. At home he uses a file manager to maintain his large collection of photos and music. Additionally he owns a small homepage and needs to transfer updated files on the FTP server. Moving and copying files are regular tasks in Simon's workflow.
+
+Not part of the target user group of Dolphin are Fred and Jeff:
+
+- **Fred**: Fred is 75 years old and is able to write e-mails and browsing the web. He is not familiar with file hierarchies and stores all his documents on the desktop.
+
+- **Jeff**: Jeff is Linux-freak since the age of 16 a few years ago. He is developer and in his spare time he acts as administrator for a small company. Jeff has two monitors to keep the overview about his huge number of opened applications.
+
+This does not mean that Fred or Jeff cannot work with Dolphin. But there might be features and concepts of Dolphin that overburden Fred. Also Jeff might miss some features which are a must-have for his daily work.
+
+Non-Intrusive Features
+-----------------------
+
+Before a feature is added in Dolphin, it is checked whether the feature is mandatory for the target user group. If this is not the case, then this does not mean that the feature cannot be added; first it must be clarified whether the feature might be non-intrusive, so that it adds value for users outside the primary target user group of Dolphin. Non-intrusive is mainly related to the user interface. A feature that adds a lot of clutter to the main menu, context menus or toolbar might harm the target user group. In this case the feature will not be added.
+
+A good example of a feature that is non-intrusive is the embedded terminal in Dolphin. It only requires one entry inside a sub menu, but adds great value for Jeff, who is not part of the target user group.
+
+Options
+-------
+
+Options are mandatory as the user "average Joe" does not exist. Still it is not the goal of Dolphin offering options for all kind of things. Again the focus is on the possible needs of the target user group. Each additional option makes it harder finding other options, so the same rules for features are applied to options too.
diff --git a/cmake/FindGem.cmake b/cmake/FindGem.cmake
new file mode 100644 (file)
index 0000000..11c9c67
--- /dev/null
@@ -0,0 +1,39 @@
+#=============================================================================
+# Copyright (c) 2019 Harald Sitter <sitter@kde.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+#    derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#=============================================================================
+
+# In this scope it's the dir we are in, in the function scope it will be the
+# caller's dir. So, keep our dir in a var.
+set(FINDGEM_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR})
+
+function(find_gem GEM_NAME)
+    set(GEM_PACKAGE "Gem:${GEM_NAME}")
+
+    configure_file(${FINDGEM_MODULES_DIR}/FindGem.cmake.in Find${GEM_PACKAGE}.cmake @ONLY)
+
+    set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_BINARY_DIR}" ${CMAKE_MODULE_PATH})
+    find_package(${GEM_PACKAGE} ${ARGN})
+endfunction()
diff --git a/cmake/FindGem.cmake.in b/cmake/FindGem.cmake.in
new file mode 100644 (file)
index 0000000..0dcc677
--- /dev/null
@@ -0,0 +1,53 @@
+#=============================================================================
+# Copyright (c) 2019 Harald Sitter <sitter@kde.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+#    derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#=============================================================================
+
+find_program(RUBY_EXE ruby)
+if(NOT RUBY_EXE)
+    message(WARNING "Could not find ruby program")
+    return()
+endif()
+
+execute_process(
+    COMMAND ${RUBY_EXE} -e "require '@GEM_NAME@'"
+    ERROR_VARIABLE ERROR_VAR
+    RESULT_VARIABLE RESULT_VAR
+)
+
+if(RESULT_VAR EQUAL 0)
+    set(@GEM_PACKAGE@_FOUND TRUE)
+else()
+    message(WARNING ${ERROR_VAR})
+    return()
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(@GEM_PACKAGE@
+    FOUND_VAR
+        @GEM_PACKAGE@_FOUND
+    REQUIRED_VARS
+        @GEM_PACKAGE@_FOUND
+)
index e0dd576793b54b22f1192a14fe1dd68074ad487b..dea10675faf54f5bfcbea6ad69ef95739c5261d2 100644 (file)
@@ -384,6 +384,13 @@ install(TARGETS kcm_dolphingeneral DESTINATION ${KDE_INSTALL_PLUGINDIR} )
 ########### install files ###############
 
 install( PROGRAMS org.kde.dolphin.desktop DESTINATION ${KDE_INSTALL_APPDIR} )
+
+install( DIRECTORY DESTINATION "${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel" )
+
+install(
+    CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ${KDE_INSTALL_FULL_APPDIR}/org.kde.dolphin.desktop ${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel/org.kde.dolphin.desktop)"
+)
+
 install( FILES settings/dolphin_directoryviewpropertysettings.kcfg
                settings/dolphin_generalsettings.kcfg
                settings/dolphin_compactmodesettings.kcfg
index fc14c79c1c270ac30bfca4422597b4147cd078b3..ebd830f7a557c37dc97d0f9fcec17f19f06db5ce 100644 (file)
@@ -2297,38 +2297,40 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
 const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
-    //  | role         | roleType       | role translation                                | group translation           | requires Baloo   | requires indexer
-        { nullptr,             NoRole,          nullptr, nullptr,                                             nullptr, nullptr,                                     false, false },
-        { "text",        NameRole,        I18N_NOOP2_NOSTRIP("@label", "Name"),             nullptr, nullptr,                                     false, false },
-        { "size",        SizeRole,        I18N_NOOP2_NOSTRIP("@label", "Size"),             nullptr, nullptr,                                     false, false },
-        { "modificationtime",        ModificationTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Modified"),             nullptr, nullptr,                                     false, false },
-        { "creationtime",        CreationTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Created"),             nullptr, nullptr,                                     false, false },
-        { "accesstime",        AccessTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Accessed"),             nullptr, nullptr,                                     false, false },
-        { "type",        TypeRole,        I18N_NOOP2_NOSTRIP("@label", "Type"),             nullptr, nullptr,                                     false, false },
-        { "rating",      RatingRole,      I18N_NOOP2_NOSTRIP("@label", "Rating"),           nullptr, nullptr,                                     true,  false },
-        { "tags",        TagsRole,        I18N_NOOP2_NOSTRIP("@label", "Tags"),             nullptr, nullptr,                                     true,  false },
-        { "comment",     CommentRole,     I18N_NOOP2_NOSTRIP("@label", "Comment"),          nullptr, nullptr,                                     true,  false },
-        { "title",       TitleRole,       I18N_NOOP2_NOSTRIP("@label", "Title"),            I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "wordCount",   WordCountRole,   I18N_NOOP2_NOSTRIP("@label", "Word Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "lineCount",   LineCountRole,   I18N_NOOP2_NOSTRIP("@label", "Line Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "imageDateTime",   ImageDateTimeRole,   I18N_NOOP2_NOSTRIP("@label", "Date Photographed"),       I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "width",       WidthRole,       I18N_NOOP2_NOSTRIP("@label", "Width"),            I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "height",      HeightRole,      I18N_NOOP2_NOSTRIP("@label", "Height"),           I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "orientation", OrientationRole, I18N_NOOP2_NOSTRIP("@label", "Orientation"),      I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "artist",      ArtistRole,      I18N_NOOP2_NOSTRIP("@label", "Artist"),           I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "genre",       GenreRole,       I18N_NOOP2_NOSTRIP("@label", "Genre"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "album",       AlbumRole,       I18N_NOOP2_NOSTRIP("@label", "Album"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "duration",    DurationRole,    I18N_NOOP2_NOSTRIP("@label", "Duration"),         I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "bitrate",     BitrateRole,     I18N_NOOP2_NOSTRIP("@label", "Bitrate"),          I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "track",       TrackRole,       I18N_NOOP2_NOSTRIP("@label", "Track"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "releaseYear", ReleaseYearRole, I18N_NOOP2_NOSTRIP("@label", "Release Year"),     I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "path",        PathRole,        I18N_NOOP2_NOSTRIP("@label", "Path"),             I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "deletiontime",DeletionTimeRole,I18N_NOOP2_NOSTRIP("@label", "Deletion Time"),    I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "destination", DestinationRole, I18N_NOOP2_NOSTRIP("@label", "Link Destination"), I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "originUrl",   OriginUrlRole,   I18N_NOOP2_NOSTRIP("@label", "Downloaded From"),  I18N_NOOP2_NOSTRIP("@label", "Other"),    true,  false },
-        { "permissions", PermissionsRole, I18N_NOOP2_NOSTRIP("@label", "Permissions"),      I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "owner",       OwnerRole,       I18N_NOOP2_NOSTRIP("@label", "Owner"),            I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "group",       GroupRole,       I18N_NOOP2_NOSTRIP("@label", "User Group"),       I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
+    //  |         role           |        roleType        |                role translation                     |            group translation               | requires Baloo | requires indexer
+        { nullptr,               NoRole,                  nullptr, nullptr,                                     nullptr, nullptr,                            false,           false },
+        { "text",                NameRole,                I18N_NOOP2_NOSTRIP("@label", "Name"),                 nullptr, nullptr,                            false,           false },
+        { "size",                SizeRole,                I18N_NOOP2_NOSTRIP("@label", "Size"),                 nullptr, nullptr,                            false,           false },
+        { "modificationtime",    ModificationTimeRole,    I18N_NOOP2_NOSTRIP("@label", "Modified"),             nullptr, nullptr,                            false,           false },
+        { "creationtime",        CreationTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Created"),              nullptr, nullptr,                            false,           false },
+        { "accesstime",          AccessTimeRole,          I18N_NOOP2_NOSTRIP("@label", "Accessed"),             nullptr, nullptr,                            false,           false },
+        { "type",                TypeRole,                I18N_NOOP2_NOSTRIP("@label", "Type"),                 nullptr, nullptr,                            false,           false },
+        { "rating",              RatingRole,              I18N_NOOP2_NOSTRIP("@label", "Rating"),               nullptr, nullptr,                            true,            false },
+        { "tags",                TagsRole,                I18N_NOOP2_NOSTRIP("@label", "Tags"),                 nullptr, nullptr,                            true,            false },
+        { "comment",             CommentRole,             I18N_NOOP2_NOSTRIP("@label", "Comment"),              nullptr, nullptr,                            true,            false },
+        { "title",               TitleRole,               I18N_NOOP2_NOSTRIP("@label", "Title"),                I18N_NOOP2_NOSTRIP("@label", "Document"),    true,            true  },
+        { "wordCount",           WordCountRole,           I18N_NOOP2_NOSTRIP("@label", "Word Count"),           I18N_NOOP2_NOSTRIP("@label", "Document"),    true,            true  },
+        { "lineCount",           LineCountRole,           I18N_NOOP2_NOSTRIP("@label", "Line Count"),           I18N_NOOP2_NOSTRIP("@label", "Document"),    true,            true  },
+        { "imageDateTime",       ImageDateTimeRole,       I18N_NOOP2_NOSTRIP("@label", "Date Photographed"),    I18N_NOOP2_NOSTRIP("@label", "Image"),       true,            true  },
+        { "width",               WidthRole,               I18N_NOOP2_NOSTRIP("@label", "Width"),                I18N_NOOP2_NOSTRIP("@label", "Image"),       true,            true  },
+        { "height",              HeightRole,              I18N_NOOP2_NOSTRIP("@label", "Height"),               I18N_NOOP2_NOSTRIP("@label", "Image"),       true,            true  },
+        { "orientation",         OrientationRole,         I18N_NOOP2_NOSTRIP("@label", "Orientation"),          I18N_NOOP2_NOSTRIP("@label", "Image"),       true,            true  },
+        { "artist",              ArtistRole,              I18N_NOOP2_NOSTRIP("@label", "Artist"),               I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "genre",               GenreRole,               I18N_NOOP2_NOSTRIP("@label", "Genre"),                I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "album",               AlbumRole,               I18N_NOOP2_NOSTRIP("@label", "Album"),                I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "duration",            DurationRole,            I18N_NOOP2_NOSTRIP("@label", "Duration"),             I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "bitrate",             BitrateRole,             I18N_NOOP2_NOSTRIP("@label", "Bitrate"),              I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "track",               TrackRole,               I18N_NOOP2_NOSTRIP("@label", "Track"),                I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "releaseYear",         ReleaseYearRole,         I18N_NOOP2_NOSTRIP("@label", "Release Year"),         I18N_NOOP2_NOSTRIP("@label", "Audio"),       true,            true  },
+        { "aspectRatio",         AspectRatioRole,         I18N_NOOP2_NOSTRIP("@label", "Aspect Ratio"),         I18N_NOOP2_NOSTRIP("@label", "Video"),       true,            true  },
+        { "frameRate",           FrameRateRole,           I18N_NOOP2_NOSTRIP("@label", "Frame Rate"),           I18N_NOOP2_NOSTRIP("@label", "Video"),       true,            true  },
+        { "path",                PathRole,                I18N_NOOP2_NOSTRIP("@label", "Path"),                 I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
+        { "deletiontime",        DeletionTimeRole,        I18N_NOOP2_NOSTRIP("@label", "Deletion Time"),        I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
+        { "destination",         DestinationRole,         I18N_NOOP2_NOSTRIP("@label", "Link Destination"),     I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
+        { "originUrl",           OriginUrlRole,           I18N_NOOP2_NOSTRIP("@label", "Downloaded From"),      I18N_NOOP2_NOSTRIP("@label", "Other"),       true,            false },
+        { "permissions",         PermissionsRole,         I18N_NOOP2_NOSTRIP("@label", "Permissions"),          I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
+        { "owner",               OwnerRole,               I18N_NOOP2_NOSTRIP("@label", "Owner"),                I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
+        { "group",               GroupRole,               I18N_NOOP2_NOSTRIP("@label", "User Group"),           I18N_NOOP2_NOSTRIP("@label", "Other"),       false,           false },
     };
 
     count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap);
index d15cfebc1b8cf14231eedd2e010d84e4f7a818f7..0f7926aae1b432f5238999a03cc7247b0015d996 100644 (file)
@@ -288,7 +288,7 @@ private:
         // User visible roles available with Baloo:
         CommentRole, TagsRole, RatingRole, WidthRole, HeightRole, ImageDateTimeRole, OrientationRole,
         WordCountRole, TitleRole, LineCountRole, ArtistRole, GenreRole, AlbumRole, DurationRole, TrackRole, ReleaseYearRole,
-        BitrateRole, OriginUrlRole,
+        BitrateRole, OriginUrlRole, AspectRatioRole, FrameRateRole,
         // Non-visible roles:
         IsDirRole, IsLinkRole, IsHiddenRole, IsExpandedRole, IsExpandableRole, ExpandedParentsCountRole,
         // Mandatory last entry:
index d3dbeb35ccdd369b36abd3488728c64f8df006c3..6fb6a5132964de116cfb64cff1a025ffb6946e0c 100644 (file)
@@ -231,6 +231,9 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
     const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
     const bool controlPressed = event->modifiers() & Qt::ControlModifier;
     const bool shiftOrControlPressed = shiftPressed || controlPressed;
+    const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End  ||
+                                   key == Qt::Key_Up   || key == Qt::Key_Down ||
+                                   key == Qt::Key_Left || key == Qt::Key_Right;
 
     const int itemCount = m_model->count();
 
@@ -246,11 +249,8 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
         }
     }
 
-    const bool selectSingleItem = m_selectionBehavior != NoSelection &&
-                                  itemCount == 1 &&
-                                  (key == Qt::Key_Home || key == Qt::Key_End  ||
-                                   key == Qt::Key_Up   || key == Qt::Key_Down ||
-                                   key == Qt::Key_Left || key == Qt::Key_Right);
+    const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
+
     if (selectSingleItem) {
         const int current = m_selectionManager->currentItem();
         m_selectionManager->setSelected(current);
@@ -458,8 +458,12 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
             }
             break;
         }
+    }
 
-        m_view->scrollToItem(index);
+    if (navigationPressed) {
+        if (index < m_view->firstVisibleIndex() || index > m_view->lastVisibleIndex()) {
+            m_view->scrollToItem(index);
+        }
     }
     return true;
 }
index 0eedf180656515769b83796d98c9b38ec378e0f2..469f0791550a9a5d152a8f8a0c35a1e44bf74582 100644 (file)
@@ -120,6 +120,8 @@ KBalooRolesProvider::KBalooRolesProvider() :
         { "album",    "album" },
         { "duration",      "duration" },
         { "bitRate", "bitrate" },
+        { "aspectRatio", "aspectRatio" },
+        { "frameRate", "frameRate" },
         { "releaseYear",    "releaseYear" },
         { "trackNumber",   "track" },
         { "originUrl", "originUrl" }
index c0ac843479f308d288a347c958f7ea6c95b66b32..b2e29e9baa598017566b6312a6d05cc0e4cef248 100755 (executable)
@@ -97,3 +97,4 @@ Terminal=false
 MimeType=inode/directory;
 InitialPreference=10
 X-DBUS-ServiceName=org.kde.dolphin
+X-KDE-Shortcuts=Meta+E
index 04357783c324c1eb96b776063d6d96250e5dc6de..0a57cf29fdb9b16fa987a19d5a00945ec6654b9d 100644 (file)
 
 #include <KFileItem>
 #include <config-baloo.h>
-#ifndef HAVE_BALOO
-class KFileMetaDataConfigurationWidget;
-#else
 namespace Baloo {
     class FileMetaDataConfigWidget;
 }
-#endif
 
 class QLabel;
 
 /**
  * @brief Dialog which allows to configure which meta data should be shown
- *        in the KFileMetaDataWidget.
+ *        in the Baloo:FileMetaDataWidget.
  */
 class FileMetaDataConfigurationDialog : public QDialog
 {
index cd8b6b38d354629a2f60b3db9747720c16427087..e5257bc72556839db0e55fa3570a98ffaaf92302 100644 (file)
@@ -185,7 +185,8 @@ void InformationPanel::showContextMenu(const QPoint &pos) {
     dateformatAction->setChecked(InformationPanelSettings::dateFormat() == static_cast<int>(Baloo::DateFormats::ShortFormat));
 
     popup.addSeparator();
-    foreach (QAction* action, customContextMenuActions()) {
+    const auto actions = customContextMenuActions();
+    for (QAction *action : actions) {
         popup.addAction(action);
     }
 
@@ -311,7 +312,7 @@ void InformationPanel::slotFilesAdded(const QString& directory)
 
 void InformationPanel::slotFilesChanged(const QStringList& files)
 {
-    foreach (const QString& fileName, files) {
+    for (const QString& fileName : files) {
         if (m_shownUrl == QUrl::fromLocalFile(fileName)) {
             showItemInfo();
             break;
@@ -321,7 +322,7 @@ void InformationPanel::slotFilesChanged(const QStringList& files)
 
 void InformationPanel::slotFilesRemoved(const QStringList& files)
 {
-    foreach (const QString& fileName, files) {
+    for (const QString& fileName : files) {
         if (m_shownUrl == QUrl::fromLocalFile(fileName)) {
             // the currently shown item has been removed, show
             // the parent directory as fallback
index 0eaa125f11906ce015180fd89be0aee882ba5177..8a8b5f9b712614222849b87446a38edb473e5c35 100644 (file)
@@ -147,7 +147,8 @@ void InformationPanelContent::showItem(const KFileItem& item)
     refreshMetaData();
 }
 
-void InformationPanelContent::refreshPreview() {
+void InformationPanelContent::refreshPreview()
+{
     // If there is a preview job, kill it to prevent that we have jobs for
     // multiple items running, and thus a race condition (bug 250787).
     if (m_previewJob) {
@@ -209,12 +210,11 @@ void InformationPanelContent::refreshPreview() {
     }
 }
 
-void InformationPanelContent::refreshMetaData() {
-    if (m_metaDataWidget) {
-        m_metaDataWidget->setDateFormat(static_cast<Baloo::DateFormats>(InformationPanelSettings::dateFormat()));
-        m_metaDataWidget->show();
-        m_metaDataWidget->setItems(KFileItemList() << m_item);
-    }
+void InformationPanelContent::refreshMetaData()
+{
+    m_metaDataWidget->setDateFormat(static_cast<Baloo::DateFormats>(InformationPanelSettings::dateFormat()));
+    m_metaDataWidget->show();
+    m_metaDataWidget->setItems(KFileItemList() << m_item);
 }
 
 void InformationPanelContent::showItems(const KFileItemList& items)
@@ -230,9 +230,7 @@ void InformationPanelContent::showItems(const KFileItemList& items)
     );
     setNameLabelText(i18ncp("@label", "%1 item selected", "%1 items selected", items.count()));
 
-    if (m_metaDataWidget) {
-        m_metaDataWidget->setItems(items);
-    }
+    m_metaDataWidget->setItems(items);
 
     m_phononWidget->hide();
 
@@ -349,9 +347,7 @@ void InformationPanelContent::adjustWidgetSizes(int width)
 
     // The metadata widget also contains a text widget which may return
     // a large preferred width.
-    if (m_metaDataWidget) {
-        m_metaDataWidget->setMaximumWidth(maxWidth);
-    }
+    m_metaDataWidget->setMaximumWidth(maxWidth);
 
     // try to increase the preview as large as possible
     m_preview->setSizeHint(QSize(maxWidth, maxWidth));
index 83fb3d80babad03faa86de1f36112d2685eab8e5..4f88b597ed034ad0287ac2c05d67a626f5acd4b3 100644 (file)
@@ -40,13 +40,9 @@ namespace KIO {
   class PreviewJob;
 }
 
-#ifndef HAVE_BALOO
-class KFileMetaDataWidget;
-#else
 namespace Baloo {
     class FileMetaDataWidget;
 }
-#endif
 
 /**
  * @brief Manages the widgets that display the meta information
@@ -138,11 +134,7 @@ private:
     PixmapViewer* m_preview;
     PhononWidget* m_phononWidget;
     QLabel* m_nameLabel;
-#ifndef HAVE_BALOO
-    KFileMetaDataWidget* m_metaDataWidget;
-#else
     Baloo::FileMetaDataWidget* m_metaDataWidget;
-#endif
     QScrollArea* m_metaDataArea;
 
     PlacesItemModel* m_placesItemModel;
index 9c41db9c585e5cf8a8bd441ee403ce9c0666254f..71c27747385e0810d191aa6b6ec75b0dc5fa4c77 100644 (file)
@@ -87,22 +87,22 @@ void DolphinSearchBox::setSearchPath(const QUrl& url)
     QFontMetrics metrics(m_fromHereButton->font());
     const int maxWidth = metrics.height() * 8;
 
-    QString location = url.fileName();
+    const QUrl cleanedUrl = url.adjusted(QUrl::RemoveUserInfo | QUrl::StripTrailingSlash);
+    QString location = cleanedUrl.fileName();
     if (location.isEmpty()) {
-        if (url.isLocalFile()) {
-            location = QStringLiteral("/");
-        } else {
-            location = url.scheme() + QLatin1String(" - ") + url.host();
-        }
+        location = cleanedUrl.toString(QUrl::PreferLocalFile);
+    }
+    if (m_fromHereButton->isChecked() && cleanedUrl.path() == QDir::homePath()) {
+        m_fromHereButton->setChecked(false);
+        m_everywhereButton->setChecked(true);
+    } else {
+        m_fromHereButton->setChecked(true);
+        m_everywhereButton->setChecked(false);
     }
 
     const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth);
     m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation));
-
-    const bool showSearchFromButtons = url.isLocalFile();
-    m_separator->setVisible(showSearchFromButtons);
-    m_fromHereButton->setVisible(showSearchFromButtons);
-    m_everywhereButton->setVisible(showSearchFromButtons);
+    m_fromHereButton->setToolTip(i18nc("action:button", "Limit search to '%1' and its subfolders", cleanedUrl.toString(QUrl::PreferLocalFile)));
 
     bool hasFacetsSupport = false;
 #ifdef HAVE_BALOO
@@ -138,9 +138,6 @@ QUrl DolphinSearchBox::urlForSearching() const
 
         QString encodedUrl;
         if (m_everywhereButton->isChecked()) {
-            // It is very unlikely, that the majority of Dolphins target users
-            // mean "the whole harddisk" instead of "my home folder" when
-            // selecting the "Everywhere" button.
             encodedUrl = QDir::homePath();
         } else {
             encodedUrl = m_searchPath.url();
@@ -402,13 +399,16 @@ void DolphinSearchBox::init()
 
     m_separator = new KSeparator(Qt::Vertical, this);
 
-    // Create "From Here" and "Everywhere"button
+    // Create "From Here" and "Your files" buttons
     m_fromHereButton = new QToolButton(this);
     m_fromHereButton->setText(i18nc("action:button", "From Here"));
     initButton(m_fromHereButton);
 
     m_everywhereButton = new QToolButton(this);
-    m_everywhereButton->setText(i18nc("action:button", "Everywhere"));
+    m_everywhereButton->setText(i18nc("action:button", "Your files"));
+    m_everywhereButton->setToolTip(i18nc("action:button", "Search in your home directory"));
+    m_everywhereButton->setIcon(QIcon::fromTheme(QStringLiteral("user-home")));
+    m_everywhereButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
     initButton(m_everywhereButton);
 
     QButtonGroup* searchLocationGroup = new QButtonGroup(this);
index 6bddb861f03d80802c7926521f77af6fd789b791..f4da53c9df65095151eb2a73e06abb2724479c91 100644 (file)
 #include <KAuthorized>
 #include <KLocalizedString>
 #include <KWindowConfig>
+#include <KMessageBox>
 
 #include <QPushButton>
 
 DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent) :
     KPageDialog(parent),
-    m_pages()
+    m_pages(),
+    m_unsavedChanges(false)
 
 {
     const QSize minSize = minimumSize();
@@ -121,6 +123,7 @@ DolphinSettingsDialog::~DolphinSettingsDialog()
 void DolphinSettingsDialog::enableApply()
 {
     buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true);
+    m_unsavedChanges = true;
 }
 
 void DolphinSettingsDialog::applySettings()
@@ -139,6 +142,7 @@ void DolphinSettingsDialog::applySettings()
         settings->save();
     }
     buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false);
+    m_unsavedChanges = false;
 }
 
 void DolphinSettingsDialog::restoreDefaults()
@@ -148,6 +152,35 @@ void DolphinSettingsDialog::restoreDefaults()
     }
 }
 
+void DolphinSettingsDialog::closeEvent(QCloseEvent* event)
+{
+    if (!m_unsavedChanges) {
+        event->accept();
+        return;
+    }
+
+    const auto response = KMessageBox::warningYesNoCancel(this,
+                                        i18n("You have unsaved changes. Do you want to apply the changes or discard them?"),
+                                        i18n("Warning"),
+                                        KStandardGuiItem::save(),
+                                        KStandardGuiItem::discard(),
+                                        KStandardGuiItem::cancel());
+    switch (response) {
+        case KMessageBox::Yes:
+            applySettings();
+            Q_FALLTHROUGH();
+        case KMessageBox::No:
+            event->accept();
+            break;
+        case KMessageBox::Cancel:
+            event->ignore();
+            break;
+        default:
+            break;
+    }
+}
+
+
 SettingsPageBase *DolphinSettingsDialog::createTrashSettingsPage(QWidget *parent)
 {
     if (!KAuthorized::authorizeControlModule(QStringLiteral("kcmtrash.desktop"))) {
index 4c8768fde5ca473b0e67f643fc2b64805b12f77d..85871b12dafc5a53fcfd478bd6f52e015b0e1eb1 100644 (file)
@@ -48,10 +48,14 @@ private slots:
     void applySettings();
     void restoreDefaults();
 
+protected:
+    void closeEvent(QCloseEvent* event) override;
+
 private:
     static SettingsPageBase *createTrashSettingsPage(QWidget *parent);
 
     QList<SettingsPageBase*> m_pages;
+    bool m_unsavedChanges;
 };
 
 #endif
index 5e4234262cc2e88346c33e09f77ac030e383a296..9e090e2cda57716bc906c2cef2e5b03e25149c00 100755 (executable)
@@ -1,37 +1,72 @@
 #!/usr/bin/env ruby
+
+# Copyright (C) 2009 Jonathan Schmidt-Dominé <devel@the-user.org>
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
 require 'fileutils'
-archive = ARGV[0]
-if archive[(archive.length - 8)..(archive.length)] == ".desktop"
-    FileUtils.rm(`qtpaths --writable-path GenericDataLocation`.strip!  + "/kservices5/ServiceMenus/" + File.basename(archive))
-    exit(0)
+
+ARCHIVE = ARGV[0]
+
+# @param log_msg [String] error that gets logged to CLI
+def fail(log_msg: nil)
+  # FIXME: this is not translated...
+  msg = 'Dolphin service menu installation failed'
+  warn log_msg if log_msg
+  system('kdialog', '--passivepopup', msg, '15')
+  abort
 end
-dir = archive + "-dir"
-# try: deinstall.sh
-# try: deinstall
-# try: installKDE4.sh
-# try: installKDE4
-# try: install.sh
-# try: install
-while true
-    dd = Dir.new(dir)
-    break if dd.count != 3
-    odir = dir
-    for entry in dd
-        dir += "/" + entry if entry != "." && entry != ".."
-    end
-    if !File.directory? dir
-        dir = odir
-        break
-    end
+
+if ARCHIVE.end_with?('.desktop')
+  data_location = `qtpaths --writable-path GenericDataLocation`.strip
+  unless $?.success?
+    fail(log_msg: "Could not get GenericDataLocation #{data_location}")
+  end
+  FileUtils.rm("#{data_location}/kservices5/ServiceMenus/#{File.basename(ARCHIVE)}")
+  exit(0)
 end
-Dir.chdir(dir)
-def fail()
-    system("kdialog --passivepopup \"Deinstallation failed\" 15")
-    exit(-1)
+dir = "#{ARCHIVE}-dir"
+
+deinstaller = nil
+%w[deinstall.sh deinstall].find do |script|
+  deinstaller = Dir.glob("#{dir}/**/#{script}")[0]
 end
-if !((File.exist?(file = "./deinstall.sh") || File.exist?(file = "./deinstall")) && system(file))
-    fail() if !File.exist?(file = "./installKDE4.sh") && !File.exist?(file = "./installKDE4") && !File.exist?(file = "./install.sh") && !File.exist?(file = "./install")
-File.new(file).chmod(0700)
-    fail() if !system(file + " --remove") && !system(file + " --delete") && !system(file + " --uninstall") && !system(file + " --deinstall")
+
+installer = nil
+%w[install-it.sh install-it installKDE4.sh installKDE4 install.sh install].find do |script|
+  installer = Dir.glob("#{dir}/**/#{script}")[0]
+end
+
+Dir.chdir(dir) do
+  deinstalled = false
+
+  [deinstaller, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
+
+  if deinstaller
+    puts "[servicemenudeinstallation]: Trying to run deinstaller #{deinstaller}"
+    deinstalled = system(deinstaller)
+  elsif installer
+    puts "[servicemenudeinstallation]: Trying to run installer #{installer}"
+    %w[--remove --delete --uninstall --deinstall].any? do |arg|
+      deinstalled = system(installer, arg)
+    end
+  end
+
+  fail unless deinstalled
 end
+
 FileUtils.rm_r(dir)
index 60b699bb6041b3cce0cd4a28f48e6a4c5c188d1d..e2d42bfbf4f0f92f48d3085a58db888df2e7133d 100755 (executable)
 #!/usr/bin/env ruby
-require 'pathname'
+
+# Copyright (C) 2009 Jonathan Schmidt-Dominé <devel@the-user.org>
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
 require 'fileutils'
-archive = ARGV[0]
-$servicedir = `qtpaths --writable-path GenericDataLocation`.strip!  + "/kservices5/ServiceMenus/"
-FileUtils.mkdir_p($servicedir) if !File.exist?($servicedir)
-if archive[(archive.length - 8)..(archive.length - 1)] == ".desktop"
-    puts "Single-File Service-Menu"
-    puts archive
-    puts $servicedir
-    FileUtils.cp(archive, $servicedir);
-    exit(0)
+
+ARCHIVE_UNCOMPRESSORS = {
+  'application/x-tar' => :"tar -xf %s -C %s",
+  'application/tar' => :"tar -xf %s -C %s",
+  'application/x-gzip' => :"tar -zxf %s -C %s",
+  'application/gzip' => :"tar -zxf %s -C %s",
+  'application/x-gzip-compressed-tar' => :"tar -zxf %s -C %s",
+  'application/gzip-compressed-tar' => :"tar -zxf %s -C %s",
+  'application/x-gzip-compressed' => :"tar -zxf %s -C %s",
+  'application/gzip-compressed' => :"tar -zxf %s -C %s",
+  'application/bzip' => :"tar -jxf %s -C %s",
+  'application/bzip2' => :"tar -jxf %s -C %s",
+  'application/x-bzip' => :"tar -jxf %s -C %s",
+  'application/x-bzip2' => :"tar -jxf %s -C %s",
+  'application/bzip-compressed' => :"tar -jxf %s -C %s",
+  'application/bzip2-compressed' => :"tar -jxf %s -C %s",
+  'application/x-bzip-compressed' => :"tar -jxf %s -C %s",
+  'application/x-bzip2-compressed' => :"tar -jxf %s -C %s",
+  'application/bzip-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/bzip2-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/x-bzip-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/x-bzip2-compressed-tar' => :"tar -jxf %s -C %s",
+  'application/zip' => :"unzip %s -d %s",
+  'application/x-zip' => :"unzip %s -d %s",
+  'application/x-zip-compressed' => :"unzip %s -d %s",
+  'multipart/x-zip' => :"unzip %s -d %s",
+  'application/tgz' => :"tar -zxf %s -C %s",
+  'application/x-compressed-gtar' => :"tar -zxf %s -C %s",
+  'file/tgz' => :"tar -zxf %s -C %s",
+  'multipart/x-tar-gz' => :"tar -zxf %s -C %s",
+  'application/x-gunzip' => :"tar -zxf %s -C %s",
+  'application/gzipped' => :"tar -zxf %s -C %s",
+  'gzip/document' => :"tar -zxf %s -C %s",
+  'application/x-bz2 ' => :"tar -jxf %s -C %s",
+  'application/x-gtar' => :"tar -xf %s -C %s",
+  'multipart/x-tar' => :"tar -xf %s -C %s"
+}
+
+ARCHIVE = ARGV[0]
+
+# @param log_msg [String] error that gets logged to CLI
+def fail(log_msg: nil)
+  # FIXME: this is not translated...
+  msg = 'Dolphin service menu installation failed'
+  warn log_msg if log_msg
+  system('kdialog', '--passivepopup', msg, '15')
+  abort
 end
-def mimeType(filename)
-    IO.popen("file --mime-type -b " + filename).gets().strip!()
+
+def mime_type(filename)
+  ret = `xdg-mime query filetype #{filename}`.strip
+  return ret if $?.success?
+
+  warn 'Failed to xdg-mime'
+  fail(log_msg: "Failed to xdg-mime #{filename}: #{ret}")
 end
-$archivetypes = { "application/x-tar" => :"tar -xf %s -C %s",
-        "application/tar" => :"tar -xf %s -C %s",
-        "application/x-gzip" => :"tar -zxf %s -C %s",
-        "application/gzip" => :"tar -zxf %s -C %s",
-        "application/x-gzip-compressed-tar" => :"tar -zxf %s -C %s",
-        "application/gzip-compressed-tar" => :"tar -zxf %s -C %s",
-        "application/x-gzip-compressed" => :"tar -zxf %s -C %s",
-        "application/gzip-compressed" => :"tar -zxf %s -C %s",
-        "application/bzip" => :"tar -jxf %s -C %s",
-        "application/bzip2" => :"tar -jxf %s -C %s",
-        "application/x-bzip" => :"tar -jxf %s -C %s",
-        "application/x-bzip2" => :"tar -jxf %s -C %s",
-        "application/bzip-compressed" => :"tar -jxf %s -C %s",
-        "application/bzip2-compressed" => :"tar -jxf %s -C %s",
-        "application/x-bzip-compressed" => :"tar -jxf %s -C %s",
-        "application/x-bzip2-compressed" => :"tar -jxf %s -C %s",
-        "application/bzip-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/bzip2-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/x-bzip-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/x-bzip2-compressed-tar" => :"tar -jxf %s -C %s",
-        "application/zip" => :"unzip %s -d %s",
-        "application/x-zip" => :"unzip %s -d %s",
-        "application/x-zip-compressed" => :"unzip %s -d %s",
-        "multipart/x-zip" => :"unzip %s -d %s",
-        "application/tgz" => :"tar -zxf %s -C %s",
-        "application/x-compressed-gtar" => :"tar -zxf %s -C %s",
-        "file/tgz" => :"tar -zxf %s -C %s",
-        "multipart/x-tar-gz" => :"tar -zxf %s -C %s",
-        "application/x-gunzip" => :"tar -zxf %s -C %s",
-        "application/gzipped" => :"tar -zxf %s -C %s",
-        "gzip/document" => :"tar -zxf %s -C %s",
-        "application/x-bz2 " => :"tar -jxf %s -C %s",
-        "application/x-gtar" => :"tar -xf %s -C %s",
-        "multipart/x-tar" => :"tar -xf %s -C %s"
-}
+
 def uncompress(filename, output)
-    system(sprintf($archivetypes[mimeType(filename)].to_s, filename, output))
+  uncompressor = ARCHIVE_UNCOMPRESSORS.fetch(mime_type(filename)).to_s
+  system(format(uncompressor, filename, output))
+rescue KeyError => e
+  # If a mimetype doesn't have an uncompressor mapped we'll get a keyerror.
+  # we'll log the error but visually report the failure.
+  fail(log_msg: "Unmapped compression format #{filename}; #{e.message}")
 end
-dir = archive + "-dir"
-if File.exist?(dir)
-    FileUtils.rm_r(dir)
+
+data_location = `qtpaths --writable-path GenericDataLocation`.strip
+unless $?.success?
+  fail(log_msg: "Could not get GenericDataLocation #{data_location}")
 end
+servicedir = "#{data_location}/kservices5/ServiceMenus/"
+
+FileUtils.mkdir_p(servicedir) unless File.exist?(servicedir)
+if ARCHIVE.end_with?('.desktop')
+  puts 'Single-File Service-Menu'
+  puts ARCHIVE
+  puts servicedir
+  FileUtils.cp(ARCHIVE, servicedir)
+  exit
+end
+
+dir = "#{ARCHIVE}-dir"
+
+FileUtils.rm_r(dir) if File.exist?(dir)
 FileUtils.mkdir(dir)
-exit(-1) if !uncompress(archive, dir)
-# try: install-it.sh
-# try: install-it
-# try: installKDE4.sh
-# try: installKDE4
-# try: install.sh
-# try: install
-while true
-    dd = Dir.new(dir)
-    break if dd.count != 3
-    odir = dir
-    for entry in dd
-        dir += "/" + entry if entry != "." && entry != ".."
-    end
-    if !File.directory? dir
-        dir = odir
-        break
-    end
+
+fail(log_msg: 'uncompress failed') unless uncompress(ARCHIVE, dir)
+
+install_it = nil
+%w[install-it.sh install-it].find do |script|
+  install_it = Dir.glob("#{dir}/**/#{script}")[0]
+end
+
+installer = nil
+%w[installKDE4.sh installKDE4 install.sh install].find do |script|
+  installer = Dir.glob("#{dir}/**/#{script}")[0]
 end
-Dir.chdir(dir)
-def fail()
-    system("kdialog --passivepopup \"Installation failed\" 15")
-    exit(-1)
+
+Dir.chdir(dir) do
+  installed = false
+
+  [install_it, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
+
+  if install_it
+    puts "[servicemenuinstallation]: Trying to run install_it #{install_it}"
+    installed = system(install_it)
+  elsif installer
+    puts "[servicemenuinstallation]: Trying to run installer #{installer}"
+    %w[--local --local-install --install].any? do |arg|
+      installed = system(installer, arg)
+    end
+  end
+
+  fail unless installed
 end
-if !((File.exist?(file = "./install-it.sh") || File.exist?(file = "./install-it")) && system(file))
-    fail() if !File.exist?(file = "./installKDE4.sh") && !File.exist?(file = "./installKDE4") && !File.exist?(file = "./install.sh") && !File.exist?(file = "./install")
-    File.new(file).chmod(0700)
-    fail() if !system(file + " --local") && !system(file + "--local-install") && !system(file + " --install")
-end    
diff --git a/src/settings/services/test/service_menu_deinstallation_test.rb b/src/settings/services/test/service_menu_deinstallation_test.rb
new file mode 100644 (file)
index 0000000..2b3514a
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+require_relative 'test_helper'
+
+require 'tmpdir'
+
+class ServiceMenuDeinstallationTest < Test::Unit::TestCase
+  def setup
+    @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}")
+    @pwdir = Dir.pwd
+    Dir.chdir(@tmpdir)
+
+    ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data')
+  end
+
+  def teardown
+    Dir.chdir(@pwdir)
+    FileUtils.rm_rf(@tmpdir)
+
+    ENV.delete('XDG_DATA_HOME')
+  end
+
+  def test_run_deinstall
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/deinstall.sh", <<-DEINSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/deinstall.sh-run
+    DEINSTALL_SH
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/install.sh-run
+    INSTALL_SH
+
+    assert(covered_system('servicemenudeinstallation', archive_base))
+
+    # deinstaller should be run
+    # installer should not be run
+    # archive_dir should have been correctly removed
+
+    assert_path_exist('deinstall.sh-run')
+    assert_path_not_exist('install.sh-run')
+    assert_path_not_exist(archive_dir)
+  end
+
+  def test_run_install_with_arg
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+if [ "$@" = "--uninstall" ]; then
+  touch #{@tmpdir}/install.sh-run
+  exit 0
+fi
+exit 1
+    INSTALL_SH
+
+    assert(covered_system('servicemenudeinstallation', archive_base))
+
+    assert_path_not_exist('deinstall.sh-run')
+    assert_path_exist('install.sh-run')
+    assert_path_not_exist(archive_dir)
+  end
+
+  # no scripts in sight
+  def test_run_fail
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    archive_base = "#{service_dir}/foo.zip"
+    archive_dir = "#{archive_base}-dir/foo-1.1/"
+    FileUtils.mkpath(archive_dir)
+
+    refute(covered_system('servicemenudeinstallation', archive_base))
+
+    # I am unsure if deinstallation really should keep the files around. But
+    # that's how it behaved originally so it's supposedly intentional
+    #   - sitter, 2019
+    assert_path_exist(archive_dir)
+  end
+
+  # For desktop files things are a bit special. There is one in .local/share/servicemenu-download
+  # and another in the actual ServiceMenus dir. The latter gets removed by the
+  # script, the former by KNS.
+  def test_run_desktop
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    downloaded_file = "#{service_dir}/foo.desktop"
+    FileUtils.mkpath(service_dir)
+    FileUtils.touch(downloaded_file)
+
+    menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/"
+    installed_file = "#{menu_dir}/foo.desktop"
+    FileUtils.mkpath(menu_dir)
+    FileUtils.touch(installed_file)
+
+    assert(covered_system('servicemenudeinstallation', downloaded_file))
+
+    assert_path_exist(downloaded_file)
+    assert_path_not_exist(installed_file)
+  end
+end
diff --git a/src/settings/services/test/service_menu_installation_test.rb b/src/settings/services/test/service_menu_installation_test.rb
new file mode 100644 (file)
index 0000000..3c9fc90
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+require_relative 'test_helper'
+
+require 'tmpdir'
+
+class ServiceMenuInstallationTest < Test::Unit::TestCase
+  def setup
+    @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}")
+    @pwdir = Dir.pwd
+    Dir.chdir(@tmpdir)
+
+    ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data')
+  end
+
+  def teardown
+    Dir.chdir(@pwdir)
+    FileUtils.rm_rf(@tmpdir)
+
+    ENV.delete('XDG_DATA_HOME')
+  end
+
+  def test_run_install
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/install-it.sh", <<-INSTALL_IT_SH)
+#!/bin/sh
+touch #{@tmpdir}/install-it.sh-run
+INSTALL_IT_SH
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+touch #{@tmpdir}/install.sh-run
+    INSTALL_SH
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    assert(covered_system('servicemenuinstallation', archive))
+
+    tar_dir = "#{service_dir}/foo.tar-dir"
+    tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
+    assert_path_exist(tar_dir)
+    assert_path_exist(tar_extract_dir)
+    assert_path_exist("#{tar_extract_dir}/install-it.sh")
+    assert_path_exist("#{tar_extract_dir}/install.sh")
+  end
+
+  def test_run_install_with_arg
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    File.write("#{archive_dir}/install.sh", <<-INSTALL_SH)
+#!/bin/sh
+if [ "$@" = "--install" ]; then
+  touch #{@tmpdir}/install.sh-run
+  exit 0
+fi
+exit 1
+    INSTALL_SH
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    assert(covered_system('servicemenuinstallation', archive))
+
+    tar_dir = "#{service_dir}/foo.tar-dir"
+    tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
+    assert_path_exist(tar_dir)
+    assert_path_exist(tar_extract_dir)
+    assert_path_not_exist("#{tar_extract_dir}/install-it.sh")
+    assert_path_exist("#{tar_extract_dir}/install.sh")
+  end
+
+  def test_run_fail
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    FileUtils.mkpath(service_dir)
+    archive = "#{service_dir}/foo.tar"
+
+    archive_dir = 'foo' # relative so tar cf is relative without fuzz
+    FileUtils.mkpath(archive_dir)
+    assert(system('tar', '-cf', archive, archive_dir))
+
+    refute(covered_system('servicemenuinstallation', archive))
+  end
+
+  def test_run_desktop
+    service_dir = File.join(Dir.pwd, 'share/servicemenu-download')
+    downloaded_file = "#{service_dir}/foo.desktop"
+    FileUtils.mkpath(service_dir)
+    FileUtils.touch(downloaded_file)
+
+    menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/"
+    installed_file = "#{menu_dir}/foo.desktop"
+    FileUtils.mkpath(menu_dir)
+    FileUtils.touch(installed_file)
+
+    assert(covered_system('servicemenuinstallation', downloaded_file))
+
+    assert_path_exist(downloaded_file)
+    assert_path_exist(installed_file)
+  end
+end
diff --git a/src/settings/services/test/test_helper.rb b/src/settings/services/test/test_helper.rb
new file mode 100644 (file)
index 0000000..9da5cf3
--- /dev/null
@@ -0,0 +1,100 @@
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+GLOBAL_COVERAGE_ROOT = File.dirname(__dir__) # ../
+
+# Simplecov is a bit meh and expects src and coverage to be under the
+# same root. Since we get run through cmake that assumption absolutely
+# doesn't hold true, so we'll need to figure out the coverage_dir relative
+# to the root and the root must always be the source :/
+# The relativity only works because internally the path gets expanded, this
+# isn't fully reliable, but oh well...
+# https://github.com/colszowka/simplecov/issues/716
+GLOBAL_COVERAGE_DIR = begin
+  require 'pathname'
+  src_path = Pathname.new(GLOBAL_COVERAGE_ROOT)
+  coverage_path = Pathname.new(File.join(Dir.pwd, 'coverage'))
+  coverage_path.relative_path_from(src_path).to_s
+end
+
+begin
+  require 'simplecov'
+
+  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
+    [
+      SimpleCov::Formatter::HTMLFormatter
+    ]
+  )
+
+  SimpleCov.start do
+    root GLOBAL_COVERAGE_ROOT
+    coverage_dir GLOBAL_COVERAGE_DIR
+  end
+rescue LoadError
+  warn 'SimpleCov not loaded'
+end
+
+# FIXME: add coverage report for jenkins?
+
+$LOAD_PATH.unshift(File.absolute_path('../', __dir__)) # ../
+
+def __test_method_name__
+  return @method_name if defined?(:@method_name)
+  index = 0
+  caller = ''
+  until caller.start_with?('test_')
+    caller = caller_locations(index, 1)[0].label
+    index += 1
+  end
+  caller
+end
+
+# system() variant which sets up merge-coverage. simplecov supports merging
+# of multiple coverage sets. we use this to get coverage metrics on the
+# binaries without having to refactor the script into runnable classes.
+def covered_system(cmd, *argv)
+  pid = fork do
+    Kernel.module_exec do
+      alias_method(:real_system, :system)
+      define_method(:system) do |*args|
+        return true if args.include?('kdialog') # disable kdialog call
+        real_system(*args)
+      end
+    end
+
+    begin
+      require 'simplecov'
+      SimpleCov.start do
+        root GLOBAL_COVERAGE_ROOT
+        coverage_dir GLOBAL_COVERAGE_DIR
+        command_name "#{cmd}_#{__test_method_name__}"
+      end
+    rescue LoadError
+      warn 'SimpleCov not loaded'
+    end
+
+    ARGV.replace(argv)
+    load "#{__dir__}/../#{cmd}"
+    puts 'all good, fork ending!'
+    exit 0
+  end
+  waitedpid, status = Process.waitpid2(pid)
+  assert_equal(pid, waitedpid)
+  status.success? # behave like system and return the success only
+end
+
+require 'test/unit'
diff --git a/src/settings/services/test/test_run.rb b/src/settings/services/test/test_run.rb
new file mode 100755 (executable)
index 0000000..b2149bb
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# This is a fancy wrapper around test_helper to prevent the collector from
+# loading the helper twice as it would occur if we ran the helper directly.
+
+require_relative 'test_helper'
+
+Test::Unit::AutoRunner.run(true, File.absolute_path(__dir__))
index 07e4257a0becf6f9fb2a4b51619fe56dbebd1e6f..8ef20cb83b7bfc04f95164d6daa0cf1468de21a6 100644 (file)
@@ -3,6 +3,18 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
 find_package(Qt5Test CONFIG REQUIRED)
 include(ECMAddTests)
 
+include(FindGem)
+
+find_gem(test-unit REQUIRED)
+set_package_properties(Gem:test-unit PROPERTIES
+    DESCRIPTION "Ruby gem 'test-unit' required for testing of servicemenu helpers.")
+
+if(BUILD_COVERAGE)
+    find_gem(simplecov)
+    set_package_properties(Gem:simplecov PROPERTIES
+        DESCRIPTION "Ruby gem 'simplecov' used for coverage statistics.")
+endif()
+
 # KItemSetTest
 ecm_add_test(kitemsettest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
 
@@ -69,3 +81,5 @@ ecm_add_test(placesitemmodeltest.cpp
 TEST_NAME placesitemmodeltest
 LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
 
+add_test(NAME servicemenutest
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../settings/services/test/test_run.rb)
index 2f258d17d94310405f9419d4900aeda350146a2b..c539799707ae1b8870f5f19c15f436399b858d49 100644 (file)
@@ -1553,22 +1553,22 @@ void KFileItemModelTest::testChangeSortRoleWhileFiltering()
 
     KIO::UDSEntry entry[3];
 
-    entry[0].insert(KIO::UDSEntry::UDS_NAME, "a.txt");
-    entry[0].insert(KIO::UDSEntry::UDS_USER, "user-b");
+    entry[0].fastInsert(KIO::UDSEntry::UDS_NAME, "a.txt");
+    entry[0].fastInsert(KIO::UDSEntry::UDS_USER, "user-b");
 
-    entry[1].insert(KIO::UDSEntry::UDS_NAME, "b.txt");
-    entry[1].insert(KIO::UDSEntry::UDS_USER, "user-c");
+    entry[1].fastInsert(KIO::UDSEntry::UDS_NAME, "b.txt");
+    entry[1].fastInsert(KIO::UDSEntry::UDS_USER, "user-c");
 
-    entry[2].insert(KIO::UDSEntry::UDS_NAME, "c.txt");
-    entry[2].insert(KIO::UDSEntry::UDS_USER, "user-a");
+    entry[2].fastInsert(KIO::UDSEntry::UDS_NAME, "c.txt");
+    entry[2].fastInsert(KIO::UDSEntry::UDS_USER, "user-a");
 
     for (int i = 0; i < 3; ++i) {
-        entry[i].insert(KIO::UDSEntry::UDS_FILE_TYPE, 0100000);    // S_IFREG might not be defined on non-Unix platforms.
-        entry[i].insert(KIO::UDSEntry::UDS_ACCESS, 07777);
-        entry[i].insert(KIO::UDSEntry::UDS_SIZE, 0);
-        entry[i].insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0);
-        entry[i].insert(KIO::UDSEntry::UDS_GROUP, "group");
-        entry[i].insert(KIO::UDSEntry::UDS_ACCESS_TIME, 0);
+        entry[i].fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, 0100000);    // S_IFREG might not be defined on non-Unix platforms.
+        entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS, 07777);
+        entry[i].fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
+        entry[i].fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0);
+        entry[i].fastInsert(KIO::UDSEntry::UDS_GROUP, "group");
+        entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, 0);
         items.append(KFileItem(entry[i], m_testDir->url(), false, true));
     }